@@ -8,6 +8,7 @@ private import semmle.code.java.frameworks.Jdbc
88private import semmle.code.java.dataflow.ExternalFlow
99private import semmle.code.java.dispatch.VirtualDispatch
1010private import semmle.code.java.dataflow.TaintTracking
11+ import CallGraph
1112
1213/** A method that is not protected from CSRF by default. */
1314abstract class CsrfUnprotectedMethod extends Method { }
@@ -24,12 +25,11 @@ private class SpringCsrfUnprotectedMethod extends CsrfUnprotectedMethod instance
2425 or
2526 this .hasAnnotation ( "org.springframework.web.bind.annotation" , "RequestMapping" ) and
2627 (
27- this .getAnAnnotation ( ) .getAnEnumConstantArrayValue ( "method" ) .getName ( ) =
28- [ "GET" , "HEAD" , "OPTIONS" , "TRACE" ]
28+ this .getMethod ( ) = [ "GET" , "HEAD" , "OPTIONS" , "TRACE" ]
2929 or
3030 // If no request type is specified with `@RequestMapping`, then all request types
3131 // are possible, so we treat this as unsafe; example: @RequestMapping(value = "test").
32- not exists ( this .getAnAnnotation ( ) . getAnArrayValue ( "method" ) )
32+ not exists ( this .getMethod ( ) )
3333 )
3434 }
3535}
@@ -49,12 +49,42 @@ private class StaplerCsrfUnprotectedMethod extends CsrfUnprotectedMethod instanc
4949 }
5050}
5151
52+ /** Gets a word that is interesting because it may indicate a state change. */
53+ private string getAnInterestingWord ( ) {
54+ result =
55+ [
56+ "post" , "put" , "patch" , "delete" , "remove" , "create" , "add" , "update" , "edit" , "publish" ,
57+ "unpublish" , "fill" , "move" , "transfer" , "logout" , "login" , "access" , "connect" , "connection" ,
58+ "register" , "submit"
59+ ]
60+ }
61+
62+ /**
63+ * Gets the regular expression used for matching strings that look like they
64+ * contain an interesting word.
65+ */
66+ private string getInterestingWordRegex ( ) {
67+ result = "(^|\\w+(?=[A-Z]))((?i)" + concat ( getAnInterestingWord ( ) , "|" ) + ")($|(?![a-z])\\w+)"
68+ }
69+
70+ /** Gets a word that is uninteresting because it likely does not indicate a state change. */
71+ private string getAnUninterestingWord ( ) {
72+ result = [ "get" , "show" , "view" , "list" , "query" , "find" ]
73+ }
74+
75+ /**
76+ * Gets the regular expression used for matching strings that look like they
77+ * contain an uninteresting word.
78+ */
79+ private string getUninterestingWordRegex ( ) {
80+ result = "^(" + concat ( getAnUninterestingWord ( ) , "|" ) + ")(?![a-z])\\w*"
81+ }
82+
5283/** 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 ( "(^|\\w+(?=[A-Z]))((?i)post|put|patch|delete|remove|create|add|update|edit|(un|)publish|fill|move|transfer|log(out|in)|access|connect(|ion)|register|submit)($|(?![a-z])\\w+)" ) and
57- not this .getName ( ) .regexpMatch ( "^(get|show|view|list|query|find)(?![a-z])\\w*" )
84+ private class NameBasedStateChangeMethod extends Method {
85+ NameBasedStateChangeMethod ( ) {
86+ this .getName ( ) .regexpMatch ( getInterestingWordRegex ( ) ) and
87+ not this .getName ( ) .regexpMatch ( getUninterestingWordRegex ( ) )
5888 }
5989}
6090
@@ -91,9 +121,9 @@ private class PreparedStatementDatabaseUpdateMethod extends DatabaseUpdateMethod
91121 }
92122}
93123
94- /** A method found via the sql-injection models which may update a SQL database. */
95- private class SqlInjectionMethod extends DatabaseUpdateMethod {
96- SqlInjectionMethod ( ) {
124+ /** A method found via the sql-injection sink models which may update a database. */
125+ private class SqlInjectionDatabaseUpdateMethod extends DatabaseUpdateMethod {
126+ SqlInjectionDatabaseUpdateMethod ( ) {
97127 exists ( DataFlow:: Node n | this = n .asExpr ( ) .( Argument ) .getCall ( ) .getCallee ( ) |
98128 sinkNode ( n , "sql-injection" ) and
99129 // do not include `executeQuery` since it is typically used with a select statement
@@ -106,9 +136,10 @@ private class SqlInjectionMethod extends DatabaseUpdateMethod {
106136}
107137
108138/**
109- * A taint-tracking configuration for reasoning about SQL queries that update a database.
139+ * A taint-tracking configuration for reasoning about SQL statements that update
140+ * a database via a call to an `execute` method.
110141 */
111- module SqlExecuteConfig implements DataFlow:: ConfigSig {
142+ private module SqlExecuteConfig implements DataFlow:: ConfigSig {
112143 predicate isSource ( DataFlow:: Node source ) {
113144 exists ( StringLiteral sl | source .asExpr ( ) = sl |
114145 sl .getValue ( ) .regexpMatch ( "^(?i)(insert|update|delete).*" )
@@ -117,16 +148,19 @@ module SqlExecuteConfig implements DataFlow::ConfigSig {
117148
118149 predicate isSink ( DataFlow:: Node sink ) {
119150 exists ( Method m | m = sink .asExpr ( ) .( Argument ) .getCall ( ) .getCallee ( ) |
120- m instanceof SqlInjectionMethod and
151+ m instanceof SqlInjectionDatabaseUpdateMethod and
121152 m .hasName ( "execute" )
122153 )
123154 }
124155}
125156
126- /** Tracks flow from SQL queries that update a database to the argument of an execute method call. */
127- module SqlExecuteFlow = TaintTracking:: Global< SqlExecuteConfig > ;
157+ /**
158+ * Tracks flow from SQL statements that update a database to the argument of
159+ * an `execute` method call.
160+ */
161+ private module SqlExecuteFlow = TaintTracking:: Global< SqlExecuteConfig > ;
128162
129- /** Provides classes and predicates representing call paths. */
163+ /** Provides classes and predicates representing call graph paths. */
130164module CallGraph {
131165 private newtype TCallPathNode =
132166 TMethod ( Method m ) or
@@ -178,20 +212,19 @@ module CallGraph {
178212 predicate edges ( CallPathNode pred , CallPathNode succ ) { pred .getASuccessor ( ) = succ }
179213}
180214
181- import CallGraph
182-
183215/**
184- * Holds if `src ` is an unprotected request handler that reaches a
185- * `sink ` that updates a database.
216+ * Holds if `sourceMethod ` is an unprotected request handler that reaches a
217+ * `sinkMethodCall ` that updates a database.
186218 */
187- predicate unprotectedDatabaseUpdate ( CallPathNode sourceMethod , CallPathNode sinkMethodCall ) {
219+ private predicate unprotectedDatabaseUpdate ( CallPathNode sourceMethod , CallPathNode sinkMethodCall ) {
188220 sourceMethod .asMethod ( ) instanceof CsrfUnprotectedMethod and
189221 exists ( CallPathNode sinkMethod |
190222 sinkMethod .asMethod ( ) instanceof DatabaseUpdateMethod and
191223 sinkMethodCall .getASuccessor ( ) = pragma [ only_bind_into ] ( sinkMethod ) and
192224 sourceMethod .getASuccessor + ( ) = pragma [ only_bind_into ] ( sinkMethodCall ) and
225+ // exclude SQL `execute` calls that do not update database
193226 if
194- sinkMethod .asMethod ( ) instanceof SqlInjectionMethod and
227+ sinkMethod .asMethod ( ) instanceof SqlInjectionDatabaseUpdateMethod and
195228 sinkMethod .asMethod ( ) .hasName ( "execute" )
196229 then
197230 exists ( SqlExecuteFlow:: PathNode executeSink | SqlExecuteFlow:: flowPath ( _, executeSink ) |
@@ -202,22 +235,22 @@ predicate unprotectedDatabaseUpdate(CallPathNode sourceMethod, CallPathNode sink
202235}
203236
204237/**
205- * Holds if `src ` is an unprotected request handler that appears to
238+ * Holds if `sourceMethod ` is an unprotected request handler that appears to
206239 * change application state based on its name.
207240 */
208- private predicate unprotectedHeuristicStateChange ( CallPathNode sourceMethod , CallPathNode sinkMethod ) {
241+ private predicate unprotectedNameBasedStateChange ( CallPathNode sourceMethod , CallPathNode sinkMethod ) {
209242 sourceMethod .asMethod ( ) instanceof CsrfUnprotectedMethod and
210- sinkMethod .asMethod ( ) instanceof NameStateChangeMethod and
243+ sinkMethod .asMethod ( ) instanceof NameBasedStateChangeMethod and
211244 sinkMethod = sourceMethod and
212245 // exclude any alerts that update a database
213246 not unprotectedDatabaseUpdate ( sourceMethod , _)
214247}
215248
216249/**
217- * Holds if `src ` is an unprotected request handler that may
250+ * Holds if `source ` is an unprotected request handler that may
218251 * change an application's state.
219252 */
220253predicate unprotectedStateChange ( CallPathNode source , CallPathNode sink ) {
221254 unprotectedDatabaseUpdate ( source , sink ) or
222- unprotectedHeuristicStateChange ( source , sink )
255+ unprotectedNameBasedStateChange ( source , sink )
223256}
0 commit comments