- qpscanner/cfcs/qpscanner.cfc
- v0.7.5
- 10 KB
- 255
1<cfcomponent output="false" displayname="qpscanner v0.7.5">
2
3
4 <cffunction name="init" returntype="any" output="false" access="public">
5 <cfargument name="StartingDir" type="String" required hint="Directory to begin scanning the contents of."/>
6 <cfargument name="OutputFormat" type="String" default="html" hint="Format of scan results: [html,wddx]"/>
7 <cfargument name="RequestTimeout" type="Numeric" default="-1" hint="Override Request Timeout, -1 to ignore"/>
8 <cfargument name="recurse" type="Boolean" default="false" hint="Also scan sub-directories?"/>
9 <cfargument name="Exclusions" type="String" default="" hint="Exclude files & directories matching this regex."/>
10 <cfargument name="scanOrderBy" type="Boolean" default="true" hint="Include ORDER BY statements in scan results?"/>
11 <cfargument name="scanQoQ" type="Boolean" default="true" hint="Include Query of Queries in scan results?"/>
12 <cfargument name="scanBuiltInFunc" type="Boolean" default="true" hint="Include Built-in Functions in scan results?"/>
13 <cfargument name="showScopeInfo" type="Boolean" default="true" hint="Show scope information in scan results?"/>
14 <cfargument name="highlightClientScopes" type="Boolean" default="true" hint="Highlight scopes with greater risk?"/>
15 <cfargument name="ClientScopes" type="String" default="form,url,client,cookie" hint="Scopes considered client scopes."/>
16 <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"/>
17 <cfargument name="BuiltInFunctions" type="String" default="now,#Arguments.NumericFunctions#"/>
18
19 <cfloop item="local.Arg" collection="#Arguments#">
20 <cfset This[Arg] = Arguments[Arg]/>
21 </cfloop>
22
23 <cfset This.ClientScopes = ListToArray(This.ClientScopes) />
24
25 <cfset This.Totals =
26 { AlertCount = 0
27 , QueryCount = 0
28 , FileCount = 0
29 , RiskFileCount = 0
30 , Time = 0
31 }/>
32
33 <cfset This.Timeout = false/>
34
35 <cfset Variables.ResultFields = "FileId,FileName,QueryAlertCount,QueryTotalCount,QueryId,QueryName,QueryStartLine,QueryEndLine,ScopeList,ContainsClientScope,QueryCode" />
36 <cfset Variables.AlertData = QueryNew(Variables.ResultFields)/>
37
38 <cfset Variables.Regexes =
39 { findQueries = new cfregex( '(?si)(?:<cfquery\b)(?:[^<]++|<(?!/cfquery>))+(?=</cfquery>)' )
40 , isQueryOfQuery = new cfregex( '(?si)dbtype\s*=\s*["'']query["'']' )
41 , killParams = new cfregex( '(?si)<cfqueryparam[^>]++>' )
42 , killCfTag = new cfregex( '(?si)<cf[a-z]{2,}[^>]*+>' ) <!--- Deliberately excludes Custom Tags and CFX --->
43 , killOrderBy = new cfregex( '(?si)\bORDER BY\b.*?$' )
44 , killBuiltIn = new cfregex( '(?si)##(#ListChangeDelims(This.BuiltInFunctions,'|')#)\([^)]*\)##' )
45 , findScopes = new cfregex( '(?si)(?<=##([a-z]{1,20}\()?)[^\(##<]+?(?=\.[^##<]+?##)' )
46 , findQueryName = new cfregex( '(?<=\bname\s{0,99}=\s{0,99})(?:"[^"]++"|''[^'']++''|[^"''\s]++)' )
47 , Newline = new cfregex( chr(10) )
48 }/>
49
50 <cfset Variables.Exclusions = [] />
51 <cfloop index="local.CurrentExclusion" list="#This.Exclusions#" delimiters=";">
52 <cfset ArrayAppend( Variables.Exclusions , new cfregex(CurrentExclusion) ) />
53 </cfloop>
54
55 <cfreturn This/>
56 </cffunction>
57
58
59
60 <cffunction name="go" returntype="any" output="false" access="public">
61 <cfset var StartTime = getTickCount()/>
62
63 <cfif This.RequestTimeout GT 0>
64 <cfsetting requesttimeout="#This.RequestTimeout#"/>
65 </cfif>
66
67 <cftry>
68 <cfset scan(This.StartingDir) />
69
70 <!--- TODO: MINOR: CHECK: Is this the best way to handle this? --->
71 <!--- If timeout occurs, ignore error and proceed. --->
72 <cfcatch>
73 <cfif find('timeout',cfcatch.message)>
74 <cfset This.Timeout = True/>
75 <cfelse>
76 <cfrethrow/>
77 </cfif>
78 </cfcatch>
79 </cftry>
80
81 <cfset This.Totals.Time = getTickCount() - StartTime/>
82
83 <cfreturn
84 { Data = Variables.AlertData
85 , Info =
86 { Totals = This.Totals
87 , Timeout = This.Timeout
88 }
89 }/>
90 </cffunction>
91
92
93
94 <cffunction name="scan" returntype="void" output="false" access="public">
95 <cfargument name="DirName" type="string" required />
96
97 <cfif DirectoryExists(Arguments.DirName)>
98
99 <cfdirectory
100 name="local.qryDir"
101 directory="#Arguments.DirName#"
102 sort="type ASC,name ASC"
103 />
104
105 <cfloop query="qryDir">
106 <cfset var CurrentTarget = Arguments.DirName & '/' & Name />
107
108 <cfset var process = true/>
109 <cfloop index="local.CurrentExclusion" array=#Variables.Exclusions#>
110 <cfif CurrentExclusion.matches( CurrentTarget )>
111 <cfset process = false/>
112 <cfbreak />
113 </cfif>
114 </cfloop>
115 <cfif NOT process> <cfcontinue/> </cfif>
116
117 <cfif (Type EQ "dir") AND This.recurse >
118
119 <cfset scan( CurrentTarget )/>
120
121 <cfelseif Type EQ "file">
122
123 <cfset var Ext = LCase(ListLast(CurrentTarget,'.')) >
124
125 <cfif Ext EQ 'cfc' OR Ext EQ 'cfm' OR Ext EQ 'cfml'>
126
127 <cfset var qryCurData = hunt( CurrentTarget )/>
128
129 <cfif qryCurData.RecordCount>
130 <cfset Variables.AlertData = QueryAppend( Variables.AlertData , qryCurData )/>
131 </cfif>
132
133 </cfif>
134
135 </cfif>
136
137 </cfloop>
138
139 <!--- This can only potentially trigger on first iteration, if This.StartingDir is a file. --->
140 <cfelseif FileExists(Arguments.DirName)>
141
142 <cfset var qryCurData = hunt( This.StartingDir )/>
143
144 <cfif qryCurData.RecordCount>
145 <cfset Variables.AlertData = QueryAppend( Variables.AlertData , qryCurData )/>
146 </cfif>
147 </cfif>
148
149 </cffunction>
150
151
152
153 <cffunction name="hunt" returntype="Query" output="false">
154 <cfargument name="FileName" type="String" required />
155 <cfset var qryResult = QueryNew(Variables.ResultFields)/>
156
157 <cffile action="read" file="#Arguments.FileName#" variable="local.FileData"/>
158
159 <cfset var Matches = Variables.Regexes['findQueries'].find( text=FileData , returntype='info' )/>
160
161 <cfloop index="CurMatch" array="#Matches#">
162
163 <cfset var QueryTagCode = ListFirst( CurMatch.Match , '>' ) />
164 <cfset var QueryCode = ListRest( CurMatch.Match , '>' ) />
165
166 <cfset var rekCode = Variables.Regexes['killParams'].replace( QueryCode , '' )/>
167 <cfset rekCode = Variables.Regexes['killCfTag'].replace( rekCode , '' )/>
168
169 <cfif NOT This.scanOrderBy>
170 <cfset rekCode = Variables.Regexes['killOrderBy'].replace( rekCode , '' )/>
171 </cfif>
172 <cfif NOT This.scanBuiltInFunc>
173 <cfset rekCode = Variables.Regexes['killBuiltIn'].replace( rekCode , '' )/>
174 </cfif>
175
176 <cfif (NOT find( '##' , rekCode ))
177 OR (NOT This.scanQoQ AND Variables.Regexes['isQueryOfQuery'].matches( CurMatch.Match ) )
178 >
179 <cfcontinue />
180 </cfif>
181
182 <cfset var CurRow = QueryAddRow(qryResult)/>
183
184 <cfset qryResult.QueryCode[CurRow] = QueryCode.replaceAll( Chr(13)&Chr(10) , Chr(10) ) />
185 <cfset qryResult.QueryCode[CurRow] = qryResult.QueryCode[CurRow].replaceAll( Chr(13) , Chr(10) ) />
186
187 <cfif This.showScopeInfo >
188 <cfset var ScopesFound = {} />
189
190 <cfloop index="local.CurScope" array="#Variables.Regexes['findScopes'].match( rekCode )#">
191 <cfset ScopesFound[CurScope] = true />
192 </cfloop>
193
194 <cfset qryResult.ContainsClientScope[CurRow] = false/>
195 <cfif This.highlightClientScopes>
196 <cfloop index="local.CurrentScope" array="#This.ClientScopes#">
197 <cfif StructKeyExists( ScopesFound , CurrentScope )>
198 <cfset qryResult.ContainsClientScope[CurRow] = true/>
199 <cfbreak/>
200 </cfif>
201 </cfloop>
202 </cfif>
203
204 <cfset qryResult.ScopeList[CurRow] = StructKeyList(ScopesFound) />
205 </cfif>
206
207 <cfset var BeforeQueryCode = left( FileData , CurMatch.Pos ) />
208
209 <cfset var StartLine = 1+Variables.Regexes['Newline'].matches( BeforeQueryCode , 'count' ) />
210 <cfset var LineCount = Variables.Regexes['Newline'].matches( CurMatch.Match , 'count' ) />
211
212 <cfset qryResult.QueryStartLine[CurRow] = StartLine/>
213 <cfset qryResult.QueryEndLine[CurRow] = StartLine + LineCount />
214 <cfset qryResult.QueryName[CurRow] = ArrayToList(Variables.Regexes['findQueryName'].match(text=QueryTagCode,limit=1)) />
215 <cfset qryResult.QueryId[CurRow] = createUuid() />
216 <cfif NOT Len( qryResult.QueryName[CurRow] )>
217 <cfset qryResult.QueryName[CurRow] = "[unknown]"/>
218 </cfif>
219
220 </cfloop>
221
222 <cfset var CurFileId = createUUID()/>
223 <cfloop query="qryResult">
224 <cfset qryResult.FileId[qryResult.CurrentRow] = CurFileId />
225 <cfset qryResult.FileName[qryResult.CurrentRow] = Arguments.FileName />
226 <cfset qryResult.QueryTotalCount[qryResult.CurrentRow] = ArrayLen(Matches) />
227 <cfset qryResult.QueryAlertCount[qryResult.CurrentRow] = qryResult.RecordCount />
228 </cfloop>
229 <cfset This.Totals.QueryCount += ArrayLen(Matches) />
230 <cfset This.Totals.AlertCount += qryResult.RecordCount />
231 <cfset This.Totals.FileCount++ />
232 <cfif qryResult.RecordCount >
233 <cfset This.Totals.RiskFileCount++ />
234 </cfif>
235
236 <cfreturn qryResult/>
237 </cffunction>
238
239
240
241 <cffunction name="QueryAppend" returntype="Query" output="false" access="private">
242 <cfargument name="QueryOne" type="Query" required />
243 <cfargument name="QueryTwo" type="Query" required />
244 <!--- Bug fix for CF9 --->
245 <cfif NOT Arguments.QueryOne.RecordCount><cfreturn Arguments.QueryTwo /></cfif>
246 <cfif NOT Arguments.QueryTwo.RecordCount><cfreturn Arguments.QueryOne /></cfif>
247 <!--- / --->
248 <cfquery name="local.Result" dbtype="Query">
249 SELECT * FROM Arguments.QueryOne
250 UNION SELECT * FROM Arguments.QueryTwo
251 </cfquery>
252 <cfreturn Result/>
253 </cffunction>
254
255
256</cfcomponent>