- qpscanner/cfcs/qpscanner.cfc
- v0.7.3.1
- 11 KB
- 309
1<cfcomponent output="false" displayname="qpscanner v0.7.3">
2
3 <cffunction name="Struct" returntype="Struct" access="private"><cfreturn Arguments/></cffunction>
4
5 <cffunction name="init" returntype="any" output="false" access="public">
6 <cfargument name="jre" type="jre-utils"/>
7 <cfargument name="StartingDir" type="String" hint="Directory to begin scanning the contents of."/>
8 <cfargument name="OutputFormat" type="String" default="html" hint="Format of scan results: [html,wddx]"/>
9 <cfargument name="RequestTimeout" type="Numeric" default="-1" hint="Override Request Timeout, -1 to ignore"/>
10 <cfargument name="recurse" type="Boolean" default="false" hint="Also scan sub-directories?"/>
11 <cfargument name="Exclusions" type="String" default="" hint="Exclude files & directories matching this regex."/>
12 <cfargument name="scanOrderBy" type="Boolean" default="true" hint="Include ORDER BY statements in scan results?"/>
13 <cfargument name="scanQoQ" type="Boolean" default="true" hint="Include Query of Queries in scan results?"/>
14 <cfargument name="scanBuiltInFunc" type="Boolean" default="true" hint="Include Built-in Functions in scan results?"/>
15 <cfargument name="showScopeInfo" type="Boolean" default="true" hint="Show scope information in scan results?"/>
16 <cfargument name="highlightClientScopes" type="Boolean" default="true" hint="Highlight scopes with greater risk?"/>
17 <cfargument name="ClientScopes" type="String" default="form,url,client,cookie" hint="Scopes considered client scopes."/>
18 <cfargument name="NumericFunctions" type="String" default="val,year,month,day,hour,minute,second,asc,dayofweek,dayofyear,daysinyear,quarter,week,fix,int,round,ceiling,gettickcount,len,min,max,pi,arraylen,listlen,structcount,listvaluecount,listvaluecountnocase,rand,randrange"/>
19 <cfargument name="BuiltInFunctions" type="String" default="now,#Arguments.NumericFunctions#"/>
20
21 <cfset var Arg = -1/>
22 <cfset var RegexList = ""/>
23 <cfset var Rex = ""/>
24 <cfset var cf = 'cf'/>
25
26 <cfloop item="Arg" collection="#Arguments#">
27 <cfset This[Arg] = Arguments[Arg]/>
28 </cfloop>
29
30 <cfset Variables.jre = Arguments.jre/>
31 <cfset StructDelete(This,'jre')/>
32
33 <cfset This.Totals = Struct
34 ( AlertCount: 0
35 , QueryCount: 0
36 , FileCount : 0
37 , DirCount : 0
38 , Time : 0
39 )/>
40
41 <cfset This.Timeout = false/>
42
43 <cfset Variables.ResultFields = "FileId,FileName,QueryAlertCount,QueryTotalCount,QueryId,QueryName,QueryStartLine,QueryEndLine,ScopeList,ContainsClientScope,QueryCode"/>
44 <cfset Variables.AlertData = QueryNew(Variables.ResultFields)/>
45
46 <cfsavecontent variable="RegexList"><cfoutput>
47 findQueries |(?si)(<#cf#query[^p]).*?(?=</#cf#query>)
48 findQueryTag |(?si)(<#cf#query(?!p)[^>]{0,300}>)
49 isQueryOfQuery |(?si)dbtype\s*=\s*["']query["']
50 killParams |(?si)<#cf#queryparam[^>]+>
51 killCfTag |(?si)<#cf#[a-z]{2,}[^>]*> <!--- Deliberately excludes Custom Tags and CFX --->
52 killOrderBy |(?si)\bORDER BY\b.*?$
53 killBuiltIn |(?si)##(#ListChangeDelims(This.BuiltInFunctions,'|')#)\([^)]*\)##
54 findScopes |(?si)(?<=##([a-z]{1,20}\()?)[^\(##<]+?(?=\.[^##<]+?##)
55 findName |(?si)(?<=(<#cf#query[^>]{0,300}\bname=")).*?(?="[^>]{0,300}>)
56 findClientScopes |(?i)\b(#ListChangeDelims(This.ClientScopes,'|')#)\b
57 isCfmlFile |(?i)\.cf(c|ml?)$
58 </cfoutput></cfsavecontent>
59
60 <cfloop index="Rex" list="#RegexList#" delimiters="#Chr(10)#">
61 <cfif Len(Trim(Rex))>
62 <cfset Variables.Regexes[ Trim(ListFirst(Rex,'|')) ] = Trim(ListRest(Rex,'|'))/>
63 </cfif>
64 </cfloop>
65
66 <cfreturn This/>
67 </cffunction>
68
69
70
71 <cffunction name="go" returntype="any" output="false" access="public">
72 <cfset var StartTime = getTickCount()/>
73
74 <cfif This.RequestTimeout GT 0>
75 <cfsetting requesttimeout="#This.RequestTimeout#"/>
76 </cfif>
77
78 <cftry>
79 <cfset scan(This.StartingDir)/>
80
81 <!--- TODO: MINOR: CHECK: Is this the best way to handle this? --->
82 <!--- If timeout occurs, ignore error and proceed. --->
83 <cfcatch>
84 <cfif find('timeout',cfcatch.message)>
85 <cfset This.Timeout = True/>
86 <cfelse>
87 <cfrethrow/>
88 </cfif>
89 </cfcatch>
90 </cftry>
91
92 <cfset This.Totals.Time = getTickCount() - StartTime/>
93 <cfreturn Struct
94 ( Data : Variables.AlertData
95 , Info : Struct
96 ( Totals : This.Totals
97 , Timeout : This.Timeout
98 )
99 )/>
100 </cffunction>
101
102
103
104 <cffunction name="scan" returntype="void" output="false" access="public">
105 <cfargument name="DirName" type="string"/>
106 <cfset var qryDir = -1/>
107 <cfset var qryCurData = -1/>
108 <cfset var CurrentTarget = -1/>
109 <cfset var process = true/>
110 <cfset var jre = Variables.jre/>
111
112 <cfif DirectoryExists(Arguments.DirName)>
113
114 <cfdirectory
115 name="qryDir"
116 directory="#Arguments.DirName#"
117 sort="type ASC,name ASC"
118 />
119
120 <cfloop query="qryDir">
121
122 <cfset CurrentTarget = Arguments.DirName & '/' & Name />
123
124
125 <cfset process = true/>
126 <cfloop index="CurrentExclusion" list="#This.Exclusions#" delimiters=";">
127 <cfif jre.matches( CurrentTarget , CurrentExclusion )>
128 <cfset process = false/>
129 </cfif>
130 </cfloop>
131
132 <cfif process>
133
134 <cfif (Type EQ "dir") AND This.recurse >
135 <cfset This.Totals.DirCount = This.Totals.DirCount + 1 />
136
137 <cfset scan( CurrentTarget )/>
138
139 <cfelseif jre.matches( CurrentTarget , Variables.Regexes.isCfmlFile )>
140 <cfset This.Totals.FileCount = This.Totals.FileCount + 1 />
141
142 <cfset qryCurData = hunt( CurrentTarget )/>
143
144 <cfif qryCurData.RecordCount>
145 <cfset Variables.AlertData = QueryAppend( Variables.AlertData , qryCurData )/>
146 </cfif>
147
148 </cfif>
149
150 </cfif>
151 </cfloop>
152
153 <!--- This can only potentially trigger on first iteration, if This.StartingDir is a file. --->
154 <cfelseif FileExists(Arguments.DirName)>
155 <cfset This.Totals.FileCount = This.Totals.FileCount + 1 />
156
157 <cfset qryCurData = hunt( This.StartingDir )/>
158
159 <cfif qryCurData.RecordCount>
160 <cfset Variables.AlertData = QueryAppend( Variables.AlertData , qryCurData )/>
161 </cfif>
162 </cfif>
163
164 </cffunction>
165
166
167
168
169 <cffunction name="hunt" returntype="Query" output="false">
170 <cfargument name="FileName" type="String"/>
171 <cfset var FileData = -1/>
172 <cfset var Matches = -1/>
173 <cfset var i = -1/>
174 <cfset var info = -1/>
175 <cfset var rekCode = -1/>
176 <cfset var QueryCode = -1/>
177 <cfset var CurRow = -1/>
178 <cfset var CurFileId = -1/>
179 <cfset var StartLine = -1/>
180 <cfset var LineCount = -1/>
181 <cfset var BeforeQueryCode = -1/>
182 <cfset var isRisk = -1/>
183 <cfset var UniqueToken = Chr(65536)/>
184 <cfset var qryResult = QueryNew(Variables.ResultFields)/>
185 <cfset var REX = Variables.Regexes/>
186 <cfset var jre = Variables.jre/>
187
188
189 <cffile action="read" file="#Arguments.FileName#" variable="FileData"/>
190
191 <cfset Matches = jre.get( FileData , REX.findQueries )/>
192 <cfset This.Totals.QueryCount = This.Totals.QueryCount + ArrayLen(Matches) />
193
194 <cfloop index="i" from="1" to="#ArrayLen(Matches)#">
195
196 <cfset QueryCode = jre.replace( Matches[i] , REX.findQueryTag , '' , 'ALL' )/>
197 <cfset rekCode = duplicate(QueryCode) />
198 <cfset rekCode = jre.replace( rekCode , REX.killParams , '' , 'ALL' )/>
199 <cfset rekCode = jre.replace( rekCode , REX.killCfTag , '' , 'ALL' )/>
200
201 <cfif NOT This.scanOrderBy>
202 <cfset rekCode = jre.replace( rekCode , REX.killOrderBy , '' , 'ALL' )/>
203 </cfif>
204 <cfif NOT This.scanBuiltInFunc>
205 <cfset rekCode = jre.replace( rekCode , REX.killBuiltIn , '' , 'ALL' )/>
206 </cfif>
207
208 <cfset isRisk = find( '##' , rekCode )/>
209
210
211 <cfif (NOT This.scanQoQ) AND jre.matches( Matches[i] , REX.isQueryOfQuery )>
212 <cfset isRisk = false/>
213 </cfif>
214
215
216 <cfif isRisk>
217 <cfset CurRow = QueryAddRow(qryResult)/>
218
219 <cfset qryResult.QueryCode[CurRow] = jre.replace( QueryCode , Chr(13) , Chr(10) , 'all' ) />
220 <cfset qryResult.QueryCode[CurRow] = jre.replace( qryResult.QueryCode[CurRow] , Chr(10)&Chr(10) , Chr(10) , 'all' ) />
221 <cfif This.showScopeInfo >
222 <cfset qryResult.ScopeList[CurRow] = ArrayToList( ArrayUnique( jre.get( rekCode , REX.findScopes ) ) ) />
223
224 <cfset qryResult.ContainsClientScope[CurRow] = false/>
225 <cfif This.highlightClientScopes>
226 <cfloop index="CurrentScope" list="#This.ClientScopes#">
227 <cfif ListFind( qryResult.ScopeList[CurRow] , CurrentScope )>
228 <cfset qryResult.ContainsClientScope[CurRow] = true/>
229 <cfbreak/>
230 </cfif>
231 </cfloop>
232 </cfif>
233 </cfif>
234
235 <!--- CF8 doesn't support get()[1] so need to use two lines: --->
236 <cfset QueryTagCode = jre.get( Matches[i] , REX.findQueryTag )/>
237 <cfset QueryTagCode = QueryTagCode[1] />
238
239 <cfset BeforeQueryCode = ListFirst ( replace ( ' '&FileData&' ' , Matches[i] , UniqueToken ) , UniqueToken )/>
240
241
242 <cfset StartLine = 1+ArrayLen( jre.get( BeforeQueryCode , chr(10) ) )/>
243 <cfset LineCount = ArrayLen( jre.get( Matches[i] , chr(10) ) )/>
244
245
246 <cfset qryResult.QueryStartLine[CurRow] = StartLine/>
247 <cfset qryResult.QueryEndLine[CurRow] = StartLine + LineCount />
248 <cfset qryResult.QueryName[CurRow] = ArrayToList( jre.get( ListLast(QueryTagCode,chr(10)) , REX.findName ) )/>
249 <cfset qryResult.QueryId[CurRow] = createUuid() />
250 <cfif NOT Len( qryResult.QueryName[CurRow] )>
251 <cfset qryResult.QueryName[CurRow] = "[unknown]"/>
252 </cfif>
253
254 </cfif>
255
256 </cfloop>
257
258 <cfset CurFileId = createUUID()/>
259 <cfloop query="qryResult">
260 <cfset qryResult.FileId[qryResult.CurrentRow] = CurFileId />
261 <cfset qryResult.FileName[qryResult.CurrentRow] = Arguments.FileName />
262 <cfset qryResult.QueryTotalCount[qryResult.CurrentRow] = ArrayLen(Matches) />
263 <cfset qryResult.QueryAlertCount[qryResult.CurrentRow] = qryResult.RecordCount />
264 </cfloop>
265 <cfset This.Totals.AlertCount = This.Totals.AlertCount + qryResult.RecordCount />
266
267 <cfreturn qryResult/>
268 </cffunction>
269
270
271
272
273
274
275
276
277 <cffunction name="ArrayUnique" returntype="Array" output="false" access="private">
278 <cfargument name="ArrayVar" type="Array"/>
279 <cfset var UniqueToken = Chr(65536)/>
280 <cfset var Result = duplicate(Arguments.ArrayVar)/>
281 <cfset ArraySort(Result,'text')/>
282 <cfset Result = ArrayToList( Result , UniqueToken )/>
283 <!--- TODO: MINOR: FIX: Using \b works for the ScopeList, but is not good enough for general use - why not using UniqueToken? --->
284 <cfset Result = REreplace( Result & UniqueToken , '(\b(.*?)\b)\1+' , '\1' , 'all' )/>
285 <cfset Result = ListToArray( Result , UniqueToken )/>
286 <!--- TODO: MINOR: Ideally, the original array order should be restored. --->
287 <cfreturn Result/>
288 </cffunction>
289
290
291
292 <cffunction name="QueryAppend" returntype="Query" output="false" access="private">
293 <cfargument name="QueryOne" type="Query"/>
294 <cfargument name="QueryTwo" type="Query"/>
295 <cfset var Result = -1/>
296 <!--- Bug fix for CF8 --->
297 <cfif NOT Arguments.QueryOne.RecordCount><cfreturn Arguments.QueryTwo /></cfif>
298 <cfif NOT Arguments.QueryTwo.RecordCount><cfreturn Arguments.QueryOne /></cfif>
299 <!--- / --->
300 <cfquery name="Result" dbtype="Query">
301 SELECT * FROM Arguments.QueryOne
302 UNION SELECT * FROM Arguments.QueryTwo
303 </cfquery>
304 <cfreturn Result/>
305 </cffunction>
306
307
308
309
310</cfcomponent>