Sorcerer's IsleCode QueryParam Scanner / files

  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>