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