How to counter this SQL Injection risk (Coldfusion)? - coldfusion

Recently, a security risk company we hired identified a SQL injection risk for a specific parameter of a function in our Coldfusion application. The argument is correctly captured as a cfargument:
<cfargument name="status" required="false" type="string">
and also correctly paramed as a cfprocparam:
<cfif isDefined('ARGUMENTS.status') AND ARGUMENTS.status NEQ "">
<cfprocparam type="in" value="#ARGUMENTS.status#" dbvarname="#search_status" cfsqltype="cf_sql_varchar">
<cfelse>
<cfprocparam type="in" null="yes" dbvarname="#search_status" cfsqltype="cf_sql_varchar">
</cfif>
And then it's used in the stored proc this way, building a where clause for a SQL query:
if (len(#search_status) > 0)
begin
set #strWhere = #strWhere + ' AND e1.cps_txt LIKE '+ '''%'+#search_status+'%'''
end
Of course, paraming to make sure the value is a varchar can't prevent a string of malicious code being entered and passed. This seems like it could easily be a larger problem, too, since it's far from the only place in our application where string arguments are passed to cfc functions and then used in SQL queries.
I'm wondering if the best approach to remedy the security risk in this one case is to compare the argument's value to all of the possible expected values (there's about twenty of them, stored in a table), and if it doesn't match one of them, then to throw an error, or just pass to the stored proc as null.

Had to tackle this at a previous employer. The trick is to pass true or false to the null argument and to avoid writing all this conditional code that calls different versions of the procparam with or without the attribute. Just always have the attribute and put the conditional logic in the value.
<cfprocparam type="in"
dbvarname="#search_status"
cfsqltype="cf_sql_varchar"
value="#ARGUMENTS.status#"
null="#!(structKeyExists(arguments, "status") AND (len(arguments.status) NEQ 0))#">
Depending on the complexity of the condition, you can simply pass it as a variable.
<cfset statusNull = true>
<cfif structKeyExists(arguments, "status") AND (len(arguments.status) NEQ 0)>
<cfset statusNull = false>
</cfif>
<cfprocparam type="in"
dbvarname="#search_status"
cfsqltype="cf_sql_varchar"
value="#ARGUMENTS.status#"
null="#statusNull#">
And I'd like to reiterate the comment from SOS that mentions dynamic SQL. We also eliminated dynamic SQL from thousands of stored procedures and replaced it with the conditional check from his linked message. This is so much easier to read and debug than dealing with acres of string concatenation code.
WHERE ( #OrderId IS NULL OR tblOrder.OrderId = #OrderId )
AND ( #OrderCustomer IS NULL OR tblOrder.OrderCustomer = #OrderCustomer )
I would also avoid adding a query just to look up valid values for status before passing the argument to the this function/proc. That just adds overhead to you system for every call to this process.

I think you pretty much answered your own question. Here's an approach I've used in the past to handle validations using accepted values stored in a small lookup table. Do this before calling your stored procedure. I'll use generic names since I don't know your table and column names.
<!--- Validation code prior to running stored proc --->
<cfif isDefined('ARGUMENTS.status') AND ARGUMENTS.status NEQ "">
<!--- Query acceptable values --->
<cfquery name="qValidateSearchStatus" datasource="myDsn">
select
search_status
from
lookup_table
</cfquery>
<cfif listFind(valueList(qValidateSearchStatus.search_status), ARGUMENTS.status)>
<cfset statusValid = true>
<cfset statusNull = false>
<cfelse>
<cfset statusValid = false>
<cfset statusNull = false>
</cfif>
<cfelse>
<cfset statusValid = true>
<cfset statusNull = true>
</cfif>
<cfif statusValid>
<!--- Call stored procedure --->
<cfstoredproc procedure="myStoredProc" datasource="myDsn">
<cfif statusNull>
<cfprocparam type="in" null="yes" dbvarname="#search_status" cfsqltype="cf_sql_varchar">
<cfelse>
<cfprocparam type="in" value="#ARGUMENTS.status#" dbvarname="#search_status" cfsqltype="cf_sql_varchar">
</cfif>
</cfstoredproc>
<cfelse>
<!--- Process error handling --->
</cfif>

Related

Coldfusion - Complex object error when trying to cfloop over query. Version discrepancy?

Preamble: So we have some working applications that we need to move over to a new server because we are retiring an older one, and as a result I've had to install a new instance of CF.
This application works fine on the older server, which is running ColdFusion version "9,0,0,251028" Standard edition (via ColdFusion Administrator).
On the newer server, I'm using CF 2016 version 2016.0.0.298074 Developer edition (It's the first thing that popped up on a google search so I went with it).
Now the issue: there is a piece of code giving an error that says:
Complex object types cannot be converted to simple values.
The expression has requested a variable or an intermediate expression result as a simple value. However, the result cannot be converted to a simple value. Simple values are strings, numbers, boolean values, and date/time values. Queries, arrays, and COM objects are examples of complex values.
The most likely cause of the error is that you tried to use a complex value as a simple one. For example, you tried to use a query variable in a cfif tag.
The error occurred in G:/Gumbo/components/modules/resource/ResourceAdmin.cfc: line 282
Called from G:/Gumbo/admin/modules/resource/action.cfm: line 34
Called from G:/Gumbo/admin/action.cfm: line 19
281 cfloop query="getseq">
282 <cfif getseq.resourceCategoryID IS temp><cfset newseq = getseq.displaySeq ><cfbreak></cfif>
283 </cfloop>
The offending line is 282. The code in question:
<cfloop query="getseq">
<cfif getseq.resourceCategoryID IS temp><cfset newseq = getseq.displaySeq ><cfbreak></cfif>
</cfloop>
From my research, I've noted that apparently cfloop doesn't work with query parameters on some versions of ColdFusion, but what I don't understand is why a NEWER version is therefore causing me this error.
So my question is:
Is there a way to reacquire this older version of CF somehow? Keep in mind I have the CF9 source folder on my older computer, but I wasn't sure if there's a way I can take the source files and move them over or manually install it or the ins and outs of that. Could it be as easy as copying the older source files into the new CF source on the newer server?
What would be an easy alternative to change the code mentioned? I am completely unfamiliar with CF as this is an older project that I inherited upon taking this job. I'd prefer to get the exact version on the newer system, but changing the code is the only viable alternative.
Any insight would be appreciated.
EDIT:
Here is the entire offending function:
<cffunction name="updateResource" access="public" output="false" displayname="Update a Resource">
<cfset VARIABLES.dateUpdated=DateAdd("d", 0, arguments.dateUpdated)>
<cfquery datasource="#this.datasource#">
update md_rlm_resource
set published=<cfqueryparam cfsqltype="cf_sql_tinyint" value="#arguments.published#">,
resourceName=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.resourceName#">,
resourceNumber=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.resourceNumber#">,
resourceAuthor=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.resourceAuthor#">,
resourceFile=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.resourceFile#">,
dateUpdated=<cfqueryparam cfsqltype="cf_sql_timestamp" value="#VARIABLES.dateUpdated#">,
shortDescription=<cfqueryparam cfsqltype="cf_sql_longvarchar" value="#arguments.shortDescription#">
where resourceID=<cfqueryparam value="#arguments.resourceID#" cfsqltype="cf_sql_integer">
</cfquery>
<cfquery name="getseq" datasource="#this.datasource#">
select displaySeq, resourceCategoryID
from md_rlm_resourcecategoryrel
where resourceID=<cfqueryparam value="#arguments.resourceID#" cfsqltype="cf_sql_integer">
</cfquery>
<cfquery datasource="#this.datasource#">
delete from md_rlm_resourcecategoryrel
where resourceID=<cfqueryparam value="#arguments.resourceID#" cfsqltype="cf_sql_integer">
</cfquery>
<cfif IsDefined("arguments.resourceCategoryIDs")>
<cfset resourceCategoryID = ArrayNew(1)>
<cfset resourceCategoryID = ListToArray(arguments.resourceCategoryIDs)>
<cfif #ListLen(arguments.resourceCategoryIDs)# gt 1>
<cfset tmp1 = #ArrayLen(resourceCategoryID)#>
<cfelse>
<cfset tmp1 = "1">
</cfif>
<cfloop INDEX="idx" FROM="1" TO="#tmp1#">
<cfset newseq = 1>
<cfif #tmp1# gt 1>
<cfset temp=resourceCategoryID[idx]>
<cfelse>
<cfset temp=resourceCategoryID>
</cfif>
<cfloop query="getseq">
<cfif getseq.resourceCategoryID IS temp><cfset newseq = getseq.displaySeq ><cfbreak></cfif>
</cfloop>
<cfquery datasource="#this.datasource#">
insert into md_rlm_resourcecategoryrel
(resourceCategoryID, resourceID, displaySeq)
values
(
<cfif #tmp1# gt 1>
<cfqueryparam CFSQLTYPE="cf_sql_integer" VALUE="#resourceCategoryID[idx]#">,
<cfelse>
<cfqueryparam CFSQLTYPE="cf_sql_integer" VALUE="#resourceCategoryID#">,
</cfif>
<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.resourceID#">,
<cfqueryparam cfsqltype="cf_sql_float" value="#newseq#">)
</cfquery>
</cfloop>
</cfif>
<cfquery datasource="#this.datasource#">
DELETE FROM md_rlm_resourceregionrel
WHERE resourceID=<cfqueryparam value="#arguments.resourceid#" cfsqltype="cf_sql_integer">
</cfquery>
<cfloop index="regionele" list="#arguments.regionID#" delimiters=",">
<cfquery datasource="#this.datasource#">
INSERT INTO md_rlm_resourceregionrel (resourceID, regionID)
VALUES (<cfqueryparam value="#arguments.resourceid#" cfsqltype="cf_sql_integer">, #regionele#)
</cfquery>
</cfloop>
</cffunction>
From this:
<cfset resourceCategoryID = ListToArray(arguments.resourceCategoryIDs)>
...
<cfif #tmp1# gt 1>
<cfset temp=resourceCategoryID[idx]>
<cfelse>
<cfset temp=resourceCategoryID><!--- temp is now an array --->
</cfif>
temp (terrible variable name, btw) could be an array.
And later you do this:
<cfif getseq.resourceCategoryID IS temp>
You cannot compare arrays with the IS operator: IS compares simple values. That's why you're seeing the error.
As an aside, you're not var-ing any of your variables in that code, which is fairly poor form, and has potential for "unexpected behaviour" in your code.

identifying the coldfusion value comng to run against query

I am passing dynamically named parameters in the url. (The number of sSearch parameters can go beyond 5 to 7 or 8 etc)
sSearch_0
sSearch_1
sSearch_2
sSearch_3
sSearch_4
sSearch_5
I want to run a loop to do a search within a query. I am trying like this:
<cfloop from="0" to="5" index="k">
<cfset counter = k>
<cfif IsDefined('url.sSearch_' & counter)>
<cfset "check_" & k = 'url.sSearch_' & counter>
</cfif>
</cfloop>
I am trying to write in a query like this:
<cfquery datasource="#coldfusionDatasource#" name="qFiltered">
SELECT *
FROM mytable
<cfif len(trim(url.sSearch))>
WHERE
<cfloop list="#listColumns#" index="thisColumn">
<cfif thisColumn neq listFirst(listColumns)> OR </cfif>
#thisColumn# LIKE <cfqueryparam cfsqltype="CF_SQL_VARCHAR"
value="%#trim(url.sSearch)#%" />
</cfloop>
</cfquery>
But it is not working. The error says check_ is undefined.
For dynamic variable naming using quotes, try:
<cfset "check_#k#" = 'url.sSearch_' & counter>
See this article

how to determine the rollback cause in a cftransaction

I have set of inserts that are wrapped in a <cftransaction> block, and I am getting a error and the insert is being rolled back.
Here is the code in question stubbed for space:
<cffunction name="InsertTCUV" access="public">
<cfargument name="vehicle required="true" type="xml" />
//Parsing the xml document here
<cftransaction>
<cfquery name="TCUVinsert datasource="mydb">
INSERT INTO tcuv
VALUES(...)
<cfquery>
<cfquery name="qLatestTCUVID" datasource="mydb">
SELECT TOP 1 tcuv_id FROM dbo.tcuv ORDER BY tcuv_id DESC
</cfquery>
<cfset curTCUVID = qLatestTCUVID.tcuv_ID>
<cfset optionsResult = insertOptions(curTCUVID,vehicle>
<cfset imagesResult = insertImages(curTCUVID,vehicle)>
<cfset standardFeaturesResult = insertStandardFeatures(curTCUVID,vehicle
</cftransaction>
</cffunction>
<cffunction name="insertOptions" access="private">
<cfargument name="TCUVID required="true type="numeric" />
<cfargument name="vehicleInfo" required="true" type="xml" />
<cfset var result = "good">
<cftry>
<cfset optionNode = xmlSearch(arguments.vehicleInfo[1], "p:RemarketingOption">
<cfloop index="i" from="1" to="#arrayLen(optionNode)#">
<cfset optionNodeNotes = XmlSearch(optionNode[#i#], "p:OptionNotes")>
<cfset optionNotes = "">
<cfloop index="j" from="1" to="#ArrayLen(optionNotesNodes)#">
<cfoutput>
<cfset optionNotes = optionNotes & " " & #optionNotesNodes[j].xmlText#>
</cfoutput>
</cfloop>
<cfquery name="insertOptions" datasource="mydb">
INSERT INTO dbo.tcuv_options
VALUES (
<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.TCUVID#">,
<cfqueryparam cfsqltype="cf_sql_varchar" value='#xmlSearch(optionNode[i], "p:OptionID")[1].xmlText#'>,
<cfqueryparam cfsqltype="cf_sql_varchar" value='#xmlSearch(optionNode[i], "p:OptionTypeCode")[1].xmlText#'>,
<cfqueryparam cfsqltype="cf_sql_varchar" value='#xmlSearch(optionNode[i], "p:OptionShortDescription")[1].xmlText#'>,
<cfqueryparam cfsqltype="cf_sql_varchar" value="#optionNotes#">
)
</cfquery>
</cfloop>
<cfcatch type="database">
//dumping cfcatch.* information
<cfset result = "error"
</cfcatch>
</cftry>
<cfreturn result>
</cffunction>
This whole thing is in a loop on the calling page, and everything works fine the first time through the loop. One the second pass, the TCUVInsert works, but I get to the insertOptions function, a coldfusion error is thrown saying that, variable insertOptions is undefined, and when I get the database, the second row isn't there, which tells me there was an error and the insert rolled back. So, there is an error with the insert of the options, and coldfusion isn't giving me the right error to diagnose it. So either I have to look in the database logs, which apparently are not setup, or try to extract the error from the cftransaction block, which I'm not sure how to do.
coldfusion 9, sql server 2008 r2
Any thoughts?
<cffunction name="insertOptions" access="private">
You are overwriting the function by using the same name for a query variable:
<cfquery name="insertOptions" datasource="imports">
Interestingly it is all because the query name was not var scoped. Functions are stored in the variables scope of the component. So by failing to localize the query name, you end up overwriting the function stored in variables.insertOptions when you run the query. Because insert statements do not return a resultset, that variable ends up being undefined. Hence the error. In this case the solution is to either scope the query name, or better yet remove it entirely (since it is not populated anyway).
Just one more reason to always var/local scope function variables - yes, query names too!

Set a variable dynamically into a structure via CFLOOP

I am trying to set a variable dynamically into a structure via CFLOOP. I have read Ben Nadal's blog post but cant seem to get the assignment correct. I would like to use dot notation and make the VIN a sub structure of values.
Here is my code:
<cfloop query="VINs">
<cfquery name="carsQue" datasource="carsData">
SELECT VIN, MODEL, MAKE, etc
FROM CarsDB
WHERE (VIN = #VIN#)
</cfquery>
<cfset carsStruct= StructNew()>
<cfset carsStruct.[VIN].MAKE = '#carsQue.MODEL#'>
<cfset carsStruct.[VIN].MODEL = '#carsQue.MAKE#'>
</cfloop>
Any guidance would be greatly appreciated,
Thanks
Running a query inside a loop is almost always a bad idea. In your case, a better option would be:
<cfif ListLen(valuelist(vins.vin)) gt 0>
<cfquery name=CarsQue datasource = "carsData">
select vin, model, make, etc
from carsDB
where vin in (<cfqueryparam cfsqltype="cf_sql_varchar"
value="#valuelist(vins.vin)#" list="true">)
</cfquery>
<cfset carsStruct = StructNew()>
<cfloop query="carsQue">
code for your struct
</cfloop>
<cfelse>
code for vins query returning no data
</cfif>
Better yet would be to get all the data with one query. You didn't provide enough information to determine if this was possible, but it often is.
Create a structure outside loop and and setting variable within loop can solve the problem. in a current scenario each time loop run its create a new structure.
you can do some thing like this
<cfset carsStruct= StructNew()>
<cfloop query="VINs">
<cfquery name="carsQue" datasource="carsData">
SELECT VIN, MODEL, MAKE, etc
FROM CarsDB
WHERE VIN = <cfqueryparam cf_sql_type="cf_sql_varchar" value="#VINs.VIN#">
</cfquery>
<cfset carsStruct[VINs.VIN].MAKE = carsQue.MODEL>
<cfset carsStruct[VINs.VIN].MODEL = carsQue.MAKE>
</cfloop>
Based on the limited information you've given you should be able to run one query and loop through that to add to your struct.
<cfset carsStruct= {}> //new struct
<cfif VINs.RecordCount> //your VINs query has records
<cfquery name="carsQueue" datasource="carsData">
SELECT VIN, MODEL, MAKE, etc
FROM CarsDB
// Quoted list of all your VINs. cfqueryparam prevents against SQL injection
WHERE VIN IN (<cfqueryparam cf_sql_type="cf_sql_varchar" value="#ValueList(VINs.VIN)#" list="true">
</cfquery>
<cfloop query="carsQueue">
<cfset carsStruct.[carsQueue.VIN].MAKE = carsQueue.MODEL>
<cfset carsStruct.[carsQueue.VIN].MODEL = carsQueue.MAKE>
</cfloop>
<cfelse>
// if VINs query return nothing a blank struct will be returned.
//You do NOT need this <cfelse> unless you are returning something when the query is blank
</cfif>

ColdFusion having multiple cfqueries in cffunction

I am trying to write a function for a survey where it pulls questions from a database. The catch is that there are both active and unactive questions. I need older questions to show up when someone views the results from an old survey.
Here is the code I am trying within a CFC:
<cffunction name="getAllQuestions" access="public" returntype="query">
<cfargument name="survey" default=0>
<cfif len(#survey#) gt 0>
<cfquery name="getsdate" datasource="blah.database">
select * from past_survey
where survey_id = #survey#
</cfquery>
<cfreturn getsdate>
</cfif>
<cfquery name="getquestions" datasource="blah.database">
select * from pool_questions
<cfif len(#survey#) eq 0>
where active_flag='Y'
<cfelse>
where <cfqueryparam value="#dateformat
(getsdate.survey_date, "yyyy/mm/dd")#"> BETWEEN start_date AND
end_date
</cfif>
order by qtn_nb
</cfquery>
<cfreturn getquestions>
</cffunction>
#survey# is the survey id which is generated by the form. What I am trying to do is that if survey has a value to run query getsdate. Then the second query would run no matter if survey has a value or not. If there is not value it should pull all active questions. If there is a value then it should check if the survey date is between the start date and end date for past questions.
Any advice on how to make this work would be greatly appreciated. Thank you in advance!
<cffunction name="getAllQuestions" access="public" returntype="struct">
<cfargument name="survey" required="true" default="0" type="numeric">
<cfset var qryReturn = ""> <!---Always var scope your variables to prevent them from leaking to other functions --->
<cfset var structReturn = structNew()>
<cfset structReturn.pastSurvey = "">
<cfset structReturn.surveyQuestions = "">
<cfif survey GT 0>
<cfquery name="qryReturn" datasource="blah.database">
SELECT *
FROM past_survey
<!--- Always cfqueryparam to prevent SQL injection attacks & also always reference the scope to prevent confusion --->
WHERE survey_id = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.survey#">
</cfquery>
<cfset structReturn.pastSurvey = qryReturn>
<cfelse>
<cfquery name="qryReturn" datasource="blah.database">
SELECT *
FROM pool_questions
<cfif arguments.survey EQ 0>
WHERE active_flag = 'Y'
<cfelse>
WHERE <cfqueryparam value="#dateformat
(getsdate.survey_date, "yyyy/mm/dd")#"> BETWEEN start_date AND
end_date
</cfif>
ORDER BY qtn_nb
</cfquery>
<cfset structReturn.surveyQuestions = qryReturn>
</cfif>
<cfreturn structReturn>
</cffunction>
You probably should be doing this in two separate functions, but I will attempt to answer your question.
My code will return a struct of queries (you can change to an array if you prefer) that returns a past survey and the survey questions
Note: In your example code, you have a few bad practices.
You are checking the length of the survey value rather than checking the value itself.
If you want to ensure that survey always has a value regardless of if it is passed or not, set requried=true and give it a default value.
Use cfqueryparam to prevent sql injection attacks
Any variables created in the function need to be var scoped to prevent them from leaking to other cffunctions in the same cfcomponent. I always do this at the top. Yes, even the name you give a cfquery needs to be var scoped.
Since you are doing a return after your first query, if the survey value is greater than 0 it will never get to the second query where it has the date check.
I see the following problems you need to address.
First, your survey argument has a default value of 0 and you are doing conditional logic on the length of it. The length of "0" is 1, so that condition will always return true.
Next, you state that you want the 2nd query to run whether the first one runs or not, but you refer to a value from the 1st query in the 2nd one. That means if the 1st query does not run, the 2nd one will crash due to an undefined variable.
Next, dateformat returns a string. Applying it the way you do in the 2nd query is at best unnecessary, and at worse, and indication that you are storing the start and end dates in pool_questions as strings. If you are attempting to strip out the time portion of the datefield in the first query, ColdFusion has a cast() function for that.
Also, scope your variables. ie - instead of <cfif len(survey), do this <cfif len(arguments.survey).
Also, var your local variables. In this case, it's the names of your two queries.
That should get you started.