Sorcerer's IsleCode QueryParam Scanner / files

  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>