how to prevent coldfusion sql-injection on order by clause - sql

Since cfqueryparam doesn't work in an order by, would using xmlformat stop sql injections?
ORDER BY #xmlformat(myVariable)#
Thanks,

http://www.petefreitag.com/item/677.cfm
A good way to get around this limitation is to use the ListFindNoCase function, to limit the sortable column names, for example:
<cfset sortable_column_list = "age,height,weight,first_name">
<cfquery ...>
SELECT first_name, age, height, weight
FROM people
ORDER BY <cfif ListFindNoCase(sortable_column_list, url.sort_column)>#url.sort_column#<cfelse>first_name</cfif>
</cfquery>

This is from a stored procedure, but translate an #ORDER_BY value to an actual database column and a #SORT_ORDER value to a SQL command.
ORDER BY
CASE WHEN #ORDER_BY = 'LENDER' AND #SORT_ORDER = 'D' THEN l.tms_name END DESC,
CASE WHEN #ORDER_BY = 'LENDER' AND #SORT_ORDER != 'D' THEN l.tms_name END,
CASE WHEN #ORDER_BY = 'LOAN_NUMBER' AND #SORT_ORDER = 'D' THEN p.Loan_Number END DESC,
CASE WHEN #ORDER_BY = 'LOAN_NUMBER' AND #SORT_ORDER != 'D' THEN p.Loan_Number END,

XML Format won't handle all cases.
The column check is good, but I'm guessing that the advantage of letting the user define what the order by is, is because you can make it more complex than just a single column. For instance, you could add several columns and an Ascending, Descending etc...
I'd suggest you make a globally available function that strips out any character that isn't a number, letter or comma. If someone did attempt to do a SQL Injection it would just fail.

<cfif refindnocas('^\w+ ?(desc|asc)?$', myVariable)>
ORDER BY #myVariable#
</cfif>
or
<cfset columnList = 'col1,col2,etc' /> <!--- might want to use in select as well --->
<cfset regexColList = replace(columnList, ',', '|', 'all') />
<cfif not refindnocas('^(#regexColList#) ?(desc|asc)?$', myVariable)>
<cfset myVariable = "DefaultSort" />
</cfif>
ORDER BY #myVariable#
or
ORDER BY #query_sort(myVariable, columnList, defaultSort)#
...
<cffunction name="query_sort">
<cfargument name="sort" />
<cfargument name="columns" />
<cfargument name"default" />
<cfset var regexcolumns = replace(columns, ',', '|', 'all') />
<cfif refindnocas('^(#regexcolumns#) ?(desc|asc)?$', sort)>
<cfreturn sort />
<cfelse>
<cfreturn default />
</cfif>
</cfargument>
etc

Another option is a slight twist on the ListFindNoCase approach. Column information can be stored in a structure. The key would be the publicly visible column name and the value is the real column name. It is a little more complex. But I like the fact that it does not require you to expose your schema. It also supports more compound statements as Dave mentioned.
<cfset sortCols = { defaultCol="DepartmentName"
, date="ReportDate"
, type="DepartmentName"
, num="EmployeeID" } />
....
SELECT Columns
FROM TableName
ORDER BY
<cfif structKeyExists(sortCols, url.sort_column)>
#sortCols[url.sort_column]#
<cfelse>
#sortCols["defaultCol"]#
</cfif>

Related

Query null values

I have the column deletedTime in my instances table.
Now I want to check in ColdFusion if the value is null.
There is only one record to be printed out with the query.
IsNull(test) is returning the correct value,
but IsNull(searchForDeletedInstances.deletedTime) returns false but the value is in the datatable null..
<cfquery datasource="hostmanager" name="searchForDeletedInstances">
SELECT deletedTime
FROM instances
WHERE instanceId = <cfqueryparam value="#instanceId#"
cfsqltype="CF_SQL_NVARCHAR">
</cfquery>
<cfloop query="searchForDeletedInstances">
<cfset test= #deletedTime#>
</cfloop>
<cfreturn IsNull(test)>
<cfreturn IsNull(searchForDeletedInstances.deletedTime)>
I would do the test in the query.
<cfquery datasource="hostmanager" name="searchForDeletedInstances">
SELECT deletedTime
FROM instances
WHERE instanceId = <cfqueryparam value="#instanceId#" cfsqltype="CF_SQL_NVARCHAR">
AND deletedTime IS NULL
</cfquery>
<cfreturn BooleanFormat(searchForDeletedInstances.recordcount)>
ColdFusion 2018 does allow for NULL values but the server/site has to be configured to use it. This may break a lot of older code.
ColdFusion before 2018 will use an empty string for NULL values.
I would change the query to only look for NULL deletedTime records. Then use the searchForDeletedInstances.recordCount value to determine if a NULL record was found. The code would look something like this:
<cfquery datasource="hostmanager" name="searchForDeletedInstances">
SELECT deletedTime
FROM instances
WHERE instanceId = <cfqueryparam value="#instanceId#" cfsqltype="CF_SQL_NVARCHAR">
AND deletedTime IS NULL
</cfquery>
<cfset nullRecord = searchForDeletedInstances.recordCount ? true : false>
<cfreturn nullRecord>
Null values from a query come back as empty strings. Test for IsDate() instead. You can always use cfdump to show the contents of your query so you can see the data and data types returned.

ColdFusion count the amount without using SQL

I am trying to count the amount without using SQL count() code
Is there a way to extract the results including the number amount over ColdFusion?
Select test
From ......
Where .....
<Cfif not test.RecordCount>
No records found
</Cfif>
<cfoutput query="test">#amount#</cfoutput>
It should display like this
TEST CountOutput
TEST A: 22
TEST B: 32
TEST B: 1
TEST C: 23
(From comments ...)
Why can't you use SQL for this? Unless there's a valid reason you can't use COUNT, it's by far the simplest way to aggregate. You don't need to know the specific values in the database table in order to perform a COUNT. Just do:
SELECT column, count(*) AS someAlias
FROM tableName
GROUP BY column
Yes, it is possible to do the same in ColdFusion code, but again unless there's a specific reason to do so, it's more efficient to use SQL.
<cfset data = {} />
<cfset names = {} />
<cfloop query="test">
<cfif not structKeyExists(data, test.name)>
<cfset data[test.name] = 0 />
<!--- if you need to preserve original case of name --->
<cfset names[test.name] = test.name />
</cfif>
<cfset data[test.name] += 1 />
</cfloop>
<cfoutput>
<cfloop collection="#data#" item="key">
#names[key]#: #data[key]#<br />
</cfloop>
</cfloop>
To get the total count without using SQL, Use
QUERYVARIABLE.RECORDCOUNT
Based on the condition used in the SQL query it will give you the count.
FOR Example,
<cfquery name="test">
SELECT * FROM database WHERE name=testA
</cfquery>
<cfdump var="#test.RECORDCOUNT#" />
Use this within a function, call it and store the return value in a stack or an array.
I strongly recommend you use a SQL Count(). I cannot imagine why you would want to do it inside a CFLoop or CFOutput, but it is possible if you use the Group attribute, and count each row that way.
Did you know that after you execute CFQuery against your SQL server to get all those detail records, you can run a "query of queries" summarize the SQL data. This article seems to explain it:
https://www.quackit.com/coldfusion/tutorial/coldfusion_query_of_queries.cfm

Coldfusion - Comma delimiter with comma inside value

I am building a list using SQL data and I am trying to make each value of the list as: 'value1',value2','value,4' and so on.
My problem is that I am using this code:
(
SELECT COUNT(ns.ticket)
FROM ns_trade ns
WHERE ns.[login]=mt.[login]
AND
<cfif qGetCommentsAccounting.recordCount gt 0>
ns.COMMENT IN ('#listChangeDelims(qGetCommentsAccounting.list_comments, "','")#')
<cfelse>
1=2
</cfif>
)as no_of_tickets_accounting
which is works perfect EXCEPT when my value has comma inside like 'value,4'.
Any suggestions how to solve that?
If both queries work on the same database, it would be way more KISS to put them together. Usually, you should try to do as much as possible within your database.
SELECT
COUNT(ns.ticket)
FROM
ns_trade ns
WHERE
ns.[login] = mt.[login]
AND
ns.COMMENT IN
(
SELECT
comment
FROM
tbl_comment
WHERE
report_type = <cfqueryparam value="#arguments.type#" cfsqltype="cf_sql_varchar">
AND
report_id = <cfqueryparam value="#arguments.report_id#" cfsqltype="cf_sql_integer">
)
I fix the problem changing my query to:
<cfquery name="qGetComments" datasource="#application.dsn#">
SELECT (STUFF((SELECT ',' +CHAR(39) +CAST(comment as varchar(50)) +CHAR(39)
FROM tbl_comment
WHERE report_type = <cfqueryparam value="#arguments.type#" cfsqltype="cf_sql_varchar">
AND report_id = <cfqueryparam value="#arguments.report_id#" cfsqltype="cf_sql_integer">
FOR XML PATH('')),1, 1, '')) as list_comments
</cfquery>
where CHR(39) is the single quote and later on the other query (wich is inside an cfsavecontent):
(SELECT COUNT(ns.ticket)
FROM ns_trade ns
WHERE ns.[login]=mt.[login]
AND <cfif qGetCommentsAccounting.recordCount gt 0> ns.COMMENT IN (#qGetCommentsAccounting.list_comments#)
<cfelse>
1=2
</cfif>)as no_of_tickets_accounting
and execute it with PreserveSingleQuotes()

Count variables in a loop

I want to count the variables and show it somewhere in my code. I have a loop:
<cfloop query="get_serial">
<cfif PROCESS_ID eq attributes.action_id> #SERIAL_NO# </cfif>
</cfloop>
and its query:
<cfquery name="get_serial" datasource="#dsn3#">
SELECT *
FROM SERVICE_GUARANTY_NEW
WHERE STOCK_ID = #attributes.action_row_id#
ORDER BY SERIAL_NO
</cfquery>
Everything works fine, but I want to count how many variables are displayed exactly. I actually want to do it in this way because I lack of database variables. Actually it is possible to get the amount of these variables from database, I just don't know which variables to use. That is why I want to count it manually.
ColdFusion exposes a variable that tells you how many rows are returned in your query, without running a loop to count them:
<cfquery name="get_serial" datasource="#dsn3#">
SELECT *
FROM SERVICE_GUARANTY_NEW
WHERE STOCK_ID = #attributes.action_row_id#
ORDER BY SERIAL_NO
</cfquery>
<cfoutput>There are #get_serial.recordCount# rows.</cfoutput>
<cfoutput query="get_serial">
<p>#get_serial.serial_no#</p>
</cfoutput>
If I understand you correctly you can do it with a counter in your loop.
<cfset counter = 0 />
<cfloop query="get_serial">
<cfif PROCESS_ID eq attributes.action_id>
#SERIAL_NO#
<cfset counter ++ />
</cfif>
</cfloop>
<cfoutput>Output #counter# times!</cfoutput>
EDIT: to answer your follow up question:
<cfset counter = 0 />
<cfsavecontent variables="myContent">
<cfloop query="get_serial">
<cfif PROCESS_ID eq attributes.action_id>
#SERIAL_NO#
<cfset counter ++ />
</cfif>
</cfloop>
</cfsavecontent>
<cfoutput>
<p>Output #counter# times!</p>
<p>#myContent#</p>
</cfoutput>
Hope that helps!
Here's another couple answers, just for fun :), although having the counter is probably the best way since you're already incurring the performance hit of using the loop.
<cfquery name="getCount" dbtype="query">
SELECT PROCESS_ID FROM get_serial WHERE PROCESS_ID = #attributes.action_id#
</cfquery>
<cfset total = getCount.RecordCount />
And using lists, always fun:
<cfset total = ListValueCount(ValueList(get_serial.PROCESS_ID), attributes.action_id) />
Point being that there are a multiple ways to solve that kind of problem, so have fun :)

SQL Array being returned as number, not String in ColdFusion

In Coldfusion, I'm using a cfc that binds one select box to another (basically, choose a State from one box, the second box is populated with County names.) The value for the County box is a 5-digit number WHICH IS FORMATTED AS TEXT (ie. the value comes from a text field.)
The problem is that I'm finding that if the value of the selected county id starts with a '0', it's been cut off.
So I get stuff like:
ID County
11223 A
2300 B (should be 02300)
Can someone help make sure that leading 0s are not cut off?
Here's the select boxes on the page:
<!--- State Name options --->
<b>State:</b><br />
<cfselect bind="cfc:states.getStates()" bindonload="true" name="search_state" id="search_state" value="StateUSAbb" display="StateName">
</cfselect><br />
<!--- County Name options --->
<b>County:</b><br />
<cfselect bind="cfc:states.getCounties({search_state})" name="search_county" id="search_county" value="FIPS_County" display="CountyName">
</cfselect>
I hate pasting the whole .cfc but pay attention to the latter part, particularly the cfloop which uses a cfset to populate array RESULT:
<cfcomponent output="false">
<!--- Get array of media types --->
<cffunction name="getStates" access="remote" returnType="array">
<!--- Define variables --->
<cfset var data="">
<cfset var result=ArrayNew(2)>
<cfset var i=0>
<!--- Get data --->
<cfquery name="data" datasource="bridges">
SELECT DISTINCT tblLoc.StateUSAbb, lkuState.StateName
FROM lkuState INNER JOIN tblLoc ON lkuState.FIPS_State = tblLoc.FIPS_State
WHERE (lkuState.StateName <> 'New Brunswick')
UNION
SELECT '' AS StateUSAbb, ' ALL' AS StateName
FROM lkuState
ORDER BY StateName
</cfquery>
<!--- Convert results to array --->
<cfloop index="i" from="1" to="#data.RecordCount#">
<cfset result[i][1]=data.StateUSAbb[i]>
<cfset result[i][2]=data.StateName[i]>
</cfloop>
<!--- And return it --->
<cfreturn result>
</cffunction>
<!--- Get counties by state --->
<cffunction name="getCounties" access="remote" returnType="array">
<cfargument name="stateabb" type="string" required="true">
<!--- Define variables --->
<cfset var data="">
<cfset var result=ArrayNew(2)>
<cfset var i=0>
<!--- Get data --->
<cfquery name="data" datasource="bridges">
SELECT '' AS FIPS_COUNTY, ' ALL' as CountyName
FROM lkuCnty
UNION
SELECT FIPS_County, CountyName
FROM lkuCnty
WHERE StateAbb = '#ARGUMENTS.stateabb#'
ORDER BY CountyName
</cfquery>
<!--- Convert results to array --->
<cfloop index="i" from="1" to="#data.RecordCount#">
<cfset result[i][1]=data.FIPS_County[i]>
<cfset result[i][2]=data.CountyName[i]>
</cfloop>
<!--- And return it --->
<cfreturn result>
</cffunction>
</cfcomponent>
If the data is a fixed length you can use NumberFormat to force leading zero's. In general, CF is typeless so there must be some underlying conversion happening that is causing the data to get corrupt. You might try forcing the value toString(), or to debug add something like a single quote as the first character in the column value (eg. SELECT '''' + FIPS_County, '''' + CountyName FROM lkuCnty) to see if they keep all their characters.
[Update]
Based on your comments about how SQL is not returning 5 char, use this updated query to go from INT to VARCHAR with leading zeros.
SELECT DISTINCT
RIGHT('00000' + CONVERT(VARCHAR(5),StateUSAbb),5),
lkuState.StateName
FROM lkuState INNER JOIN tblLoc ON lkuState.FIPS_State = tblLoc.FIPS_State
WHERE (lkuState.StateName <> 'New Brunswick')
UNION
SELECT '' AS StateUSAbb,
' ALL' AS StateName
FROM lkuState
ORDER BY StateName
append a space to the end of the number, then CF will treat it as a string and no leading 0 will be chopped.
simple workaround: <cfset result[i][1]=data.StateUSAbb[i] & " ">
btw, you know that query object is supported for populating cfselect right? So you don't even need the loop. You can do the same workaround but in SQL inside your cfquery
UPDATE: anyway, the idea is that if u want to preserve the leading 0 and keep using CF's built in serializeJSON() or calling the cfc remote method in JSON style (which will internally invoke serializeJSON(), you can append a space so CF will treat it as a string and leading 0 will be preserved. If your script somehow must need "012345" with no trailing space, then look for another JSON seralizer from riaforge or cflib.
You're sure the data returned from your query is a text string which contains the leading zero, rather than just the integer value? Regardless, I think Zachary's suggestion of NumberFormat(x, "00000") is the way to go.