- qpscanner/cfcs/qpscanner.cfc
- v0.7.4
- 11 KB
- 297
1<cfcomponent output="false" displayname="qpscanner v0.7.4">
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 =
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\b)(?:[^<]++|<(?!/#cf#query>))+(?=</#cf#query>)
48 findQueryTag |(?si)(<#cf#query[^p][^>]++>)
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 findClientScopes |(?i)\b(#ListChangeDelims(This.ClientScopes,'|')#)\b
56 </cfoutput></cfsavecontent>
57
58 <cfloop index="Rex" list="#RegexList#" delimiters="#Chr(10)#">
59 <cfif Len(Trim(Rex))>
60 <cfset Variables.Regexes[ Trim(ListFirst(Rex,'|')) ] = Trim(ListRest(Rex,'|'))/>
61 </cfif>
62 </cfloop>
63
64 <cfreturn This/>
65 </cffunction>
66
67
68
69 <cffunction name="go" returntype="any" output="false" access="public">
70 <cfset var StartTime = getTickCount()/>
71
72 <cfif This.RequestTimeout GT 0>
73 <cfsetting requesttimeout="#This.RequestTimeout#"/>
74 </cfif>
75
76 <cftry>
77 <cfset scan(This.StartingDir)/>
78
79 <!--- TODO: MINOR: CHECK: Is this the best way to handle this? --->
80 <!--- If timeout occurs, ignore error and proceed. --->
81 <cfcatch>
82 <cfif find('timeout',cfcatch.message)>
83 <cfset This.Timeout = True/>
84 <cfelse>
85 <cfrethrow/>
86 </cfif>
87 </cfcatch>
88 </cftry>
89
90 <cfset This.Totals.Time = getTickCount() - StartTime/>
91 <cfreturn
92 { Data = Variables.AlertData
93 , Info =
94 { Totals = This.Totals
95 , Timeout = This.Timeout
96 }
97 }/>
98 </cffunction>
99
100
101
102 <cffunction name="scan" returntype="void" output="false" access="public">
103 <cfargument name="DirName" type="string"/>
104 <cfset var qryDir = -1/>
105 <cfset var qryCurData = -1/>
106 <cfset var CurrentTarget = -1/>
107 <cfset var process = true/>
108 <cfset var jre = Variables.jre/>
109 <cfset var Ext = 0 />
110
111 <cfif DirectoryExists(Arguments.DirName)>
112
113 <cfdirectory
114 name="qryDir"
115 directory="#Arguments.DirName#"
116 sort="type ASC,name ASC"
117 />
118
119 <cfloop query="qryDir">
120
121 <cfset CurrentTarget = Arguments.DirName & '/' & Name />
122
123
124 <cfset process = true/>
125 <cfloop index="CurrentExclusion" list="#This.Exclusions#" delimiters=";">
126 <cfif jre.matches( CurrentTarget , CurrentExclusion )>
127 <cfset process = false/>
128 </cfif>
129 </cfloop>
130
131 <cfif process>
132
133 <cfif (Type EQ "dir") AND This.recurse >
134 <cfset This.Totals.DirCount = This.Totals.DirCount + 1 />
135
136 <cfset scan( CurrentTarget )/>
137
138 <cfelse>
139 <cfset Ext = LCase(ListLast(CurrentTarget,'.')) >
140
141 <cfif Ext EQ 'cfc' OR Ext EQ 'cfm' OR Ext EQ 'cfml'>
142
143 <cfset This.Totals.FileCount = This.Totals.FileCount + 1 />
144
145 <cfset qryCurData = hunt( CurrentTarget )/>
146
147 <cfif qryCurData.RecordCount>
148 <cfset Variables.AlertData = QueryAppend( Variables.AlertData , qryCurData )/>
149 </cfif>
150
151 </cfif>
152
153 </cfif>
154
155 </cfif>
156 </cfloop>
157
158 <!--- This can only potentially trigger on first iteration, if This.StartingDir is a file. --->
159 <cfelseif FileExists(Arguments.DirName)>
160 <cfset This.Totals.FileCount = This.Totals.FileCount + 1 />
161
162 <cfset qryCurData = hunt( This.StartingDir )/>
163
164 <cfif qryCurData.RecordCount>
165 <cfset Variables.AlertData = QueryAppend( Variables.AlertData , qryCurData )/>
166 </cfif>
167 </cfif>
168
169 </cffunction>
170
171
172
173
174 <cffunction name="hunt" returntype="Query" output="false">
175 <cfargument name="FileName" type="String"/>
176 <cfset var FileData = -1/>
177 <cfset var Matches = -1/>
178 <cfset var i = -1/>
179 <cfset var info = -1/>
180 <cfset var rekCode = -1/>
181 <cfset var QueryCode = -1/>
182 <cfset var CurRow = -1/>
183 <cfset var CurFileId = -1/>
184 <cfset var StartLine = -1/>
185 <cfset var LineCount = -1/>
186 <cfset var BeforeQueryCode = -1/>
187 <cfset var isRisk = -1/>
188 <cfset var UniqueToken = Chr(65536)/>
189 <cfset var qryResult = QueryNew(Variables.ResultFields)/>
190 <cfset var REX = Variables.Regexes/>
191 <cfset var jre = Variables.jre/>
192
193
194 <cffile action="read" file="#Arguments.FileName#" variable="FileData"/>
195
196 <cfset Matches = jre.get( FileData , REX.findQueries )/>
197 <cfset This.Totals.QueryCount = This.Totals.QueryCount + ArrayLen(Matches) />
198
199 <cfloop index="i" from="1" to="#ArrayLen(Matches)#">
200
201 <cfset QueryCode = jre.replace( Matches[i] , REX.findQueryTag , '' , 'ALL' )/>
202 <cfset rekCode = duplicate(QueryCode) />
203 <cfset rekCode = jre.replace( rekCode , REX.killParams , '' , 'ALL' )/>
204 <cfset rekCode = jre.replace( rekCode , REX.killCfTag , '' , 'ALL' )/>
205
206 <cfif NOT This.scanOrderBy>
207 <cfset rekCode = jre.replace( rekCode , REX.killOrderBy , '' , 'ALL' )/>
208 </cfif>
209 <cfif NOT This.scanBuiltInFunc>
210 <cfset rekCode = jre.replace( rekCode , REX.killBuiltIn , '' , 'ALL' )/>
211 </cfif>
212
213 <cfset isRisk = find( '##' , rekCode )/>
214
215
216 <cfif (NOT This.scanQoQ) AND jre.matches( Matches[i] , REX.isQueryOfQuery )>
217 <cfset isRisk = false/>
218 </cfif>
219
220
221 <cfif isRisk>
222 <cfset CurRow = QueryAddRow(qryResult)/>
223
224 <cfset qryResult.QueryCode[CurRow] = jre.replace( QueryCode , Chr(13) , Chr(10) , 'all' ) />
225 <cfset qryResult.QueryCode[CurRow] = jre.replace( qryResult.QueryCode[CurRow] , Chr(10)&Chr(10) , Chr(10) , 'all' ) />
226 <cfif This.showScopeInfo >
227 <cfset qryResult.ScopeList[CurRow] = [] />
228 <cfloop index="CurScope" array="#jre.get( rekCode , REX.findScopes )#">
229 <cfif NOT ArrayFind(qryResult.ScopeList[CurRow],CurScope)>
230 <cfset ArrayAppend(qryResult.ScopeList[CurRow],CurScope)>
231 </cfif>
232 </cfloop>
233
234 <cfset qryResult.ContainsClientScope[CurRow] = false/>
235 <cfif This.highlightClientScopes>
236 <cfloop index="CurrentScope" list="#This.ClientScopes#">
237 <cfif ArrayFind( qryResult.ScopeList[CurRow] , CurrentScope )>
238 <cfset qryResult.ContainsClientScope[CurRow] = true/>
239 <cfbreak/>
240 </cfif>
241 </cfloop>
242 </cfif>
243
244 <cfset qryResult.ScopeList[CurRow] = ArrayToList(qryResult.ScopeList[CurRow]) />
245 </cfif>
246
247 <cfset QueryTagCode = jre.getFirst( Matches[i] , REX.findQueryTag )/>
248
249 <cfset BeforeQueryCode = ListFirst ( replace ( ' '&FileData&' ' , Matches[i] , UniqueToken ) , UniqueToken )/>
250
251 <cfset StartLine = 1+ArrayLen( jre.get( BeforeQueryCode , chr(10) ) )/>
252 <cfset LineCount = ArrayLen( jre.get( Matches[i] , chr(10) ) )/>
253
254
255 <cfset qryResult.QueryStartLine[CurRow] = StartLine/>
256 <cfset qryResult.QueryEndLine[CurRow] = StartLine + LineCount />
257 <cfset qryResult.QueryName[CurRow] = jre.getFirst(QueryTagCode,'(?<=\bname\s{0,10}=\s{0,10}(["'']))\S(?=\1)') />
258 <cfset qryResult.QueryId[CurRow] = createUuid() />
259 <cfif NOT Len( qryResult.QueryName[CurRow] )>
260 <cfset qryResult.QueryName[CurRow] = "[unknown]"/>
261 </cfif>
262
263 </cfif>
264
265 </cfloop>
266
267 <cfset CurFileId = createUUID()/>
268 <cfloop query="qryResult">
269 <cfset qryResult.FileId[qryResult.CurrentRow] = CurFileId />
270 <cfset qryResult.FileName[qryResult.CurrentRow] = Arguments.FileName />
271 <cfset qryResult.QueryTotalCount[qryResult.CurrentRow] = ArrayLen(Matches) />
272 <cfset qryResult.QueryAlertCount[qryResult.CurrentRow] = qryResult.RecordCount />
273 </cfloop>
274 <cfset This.Totals.AlertCount = This.Totals.AlertCount + qryResult.RecordCount />
275
276 <cfreturn qryResult/>
277 </cffunction>
278
279
280 <cffunction name="QueryAppend" returntype="Query" output="false" access="private">
281 <cfargument name="QueryOne" type="Query"/>
282 <cfargument name="QueryTwo" type="Query"/>
283 <cfset var Result = -1/>
284 <!--- Bug fix for CF9 --->
285 <cfif NOT Arguments.QueryOne.RecordCount><cfreturn Arguments.QueryTwo /></cfif>
286 <cfif NOT Arguments.QueryTwo.RecordCount><cfreturn Arguments.QueryOne /></cfif>
287 <!--- / --->
288 <cfquery name="Result" dbtype="Query">
289 SELECT * FROM Arguments.QueryOne
290 UNION SELECT * FROM Arguments.QueryTwo
291 </cfquery>
292 <cfreturn Result/>
293 </cffunction>
294
295
296
297
298</cfcomponent>