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