@@ -49,6 +49,15 @@ private class StaplerCsrfUnprotectedMethod extends CsrfUnprotectedMethod instanc
4949 }
5050}
5151
52+ /** A method that appears to change application state based on its name. */
53+ private class NameStateChangeMethod extends Method {
54+ NameStateChangeMethod ( ) {
55+ this .getName ( )
56+ .regexpMatch ( ".*(?i)(post|put|patch|delete|remove|create|add|update|edit|publish|unpublish|fill|move|transfer|log(out|in)|access|connect|register|submit|den(y|ied)).*" ) and
57+ not this .getName ( ) .regexpMatch ( "^(get|show|view|list|query|find).*" )
58+ }
59+ }
60+
5261/** A method that updates a database. */
5362abstract class DatabaseUpdateMethod extends Method { }
5463
@@ -162,20 +171,46 @@ module CallGraph {
162171
163172import CallGraph
164173
165- /** Holds if `src` is an unprotected request handler that reaches a state-changing `sink`. */
166- predicate unprotectedStateChange ( CallPathNode src , CallPathNode sink , CallPathNode sinkPred ) {
167- src .asMethod ( ) instanceof CsrfUnprotectedMethod and
168- sink .asMethod ( ) instanceof DatabaseUpdateMethod and
169- sinkPred .getASuccessor ( ) = sink and
170- src .getASuccessor + ( ) = sinkPred and
171- if
172- sink .asMethod ( ) instanceof SqlInjectionMethod and
173- sink .asMethod ( ) .hasName ( "execute" )
174- then
175- exists ( SqlExecuteFlow:: PathNode executeSrc , SqlExecuteFlow:: PathNode executeSink |
176- SqlExecuteFlow:: flowPath ( executeSrc , executeSink )
177- |
178- sinkPred .asCall ( ) = executeSink .getNode ( ) .asExpr ( ) .( Argument ) .getCall ( )
179- )
180- else any ( )
174+ /**
175+ * Holds if `src` is an unprotected request handler that reaches a
176+ * `sink` that updates a database.
177+ */
178+ predicate unprotectedDatabaseUpdate ( CallPathNode sourceMethod , CallPathNode sinkMethodCall ) {
179+ sourceMethod .asMethod ( ) instanceof CsrfUnprotectedMethod and
180+ exists ( CallPathNode sinkMethod |
181+ sinkMethod .asMethod ( ) instanceof DatabaseUpdateMethod and
182+ sinkMethodCall .getASuccessor ( ) = sinkMethod and
183+ sourceMethod .getASuccessor + ( ) = sinkMethodCall and
184+ if
185+ sinkMethod .asMethod ( ) instanceof SqlInjectionMethod and
186+ sinkMethod .asMethod ( ) .hasName ( "execute" )
187+ then
188+ exists ( SqlExecuteFlow:: PathNode executeSrc , SqlExecuteFlow:: PathNode executeSink |
189+ SqlExecuteFlow:: flowPath ( executeSrc , executeSink )
190+ |
191+ sinkMethodCall .asCall ( ) = executeSink .getNode ( ) .asExpr ( ) .( Argument ) .getCall ( )
192+ )
193+ else any ( )
194+ )
195+ }
196+
197+ /**
198+ * Holds if `src` is an unprotected request handler that appears to
199+ * change application state based on its name.
200+ */
201+ private predicate unprotectedHeuristicStateChange ( CallPathNode sourceMethod , CallPathNode sinkMethod ) {
202+ sourceMethod .asMethod ( ) instanceof CsrfUnprotectedMethod and
203+ sinkMethod .asMethod ( ) instanceof NameStateChangeMethod and
204+ sinkMethod = sourceMethod and
205+ // exclude any alerts that update a database
206+ not unprotectedDatabaseUpdate ( sourceMethod , _)
207+ }
208+
209+ /**
210+ * Holds if `src` is an unprotected request handler that may
211+ * change an application's state.
212+ */
213+ predicate unprotectedStateChange ( CallPathNode source , CallPathNode sink ) {
214+ unprotectedDatabaseUpdate ( source , sink ) or
215+ unprotectedHeuristicStateChange ( source , sink )
181216}
0 commit comments