@@ -8,6 +8,7 @@ private import semmle.code.java.frameworks.Jdbc
8
8
private import semmle.code.java.dataflow.ExternalFlow
9
9
private import semmle.code.java.dispatch.VirtualDispatch
10
10
private import semmle.code.java.dataflow.TaintTracking
11
+ import CallGraph
11
12
12
13
/** A method that is not protected from CSRF by default. */
13
14
abstract class CsrfUnprotectedMethod extends Method { }
@@ -24,12 +25,11 @@ private class SpringCsrfUnprotectedMethod extends CsrfUnprotectedMethod instance
24
25
or
25
26
this .hasAnnotation ( "org.springframework.web.bind.annotation" , "RequestMapping" ) and
26
27
(
27
- this .getAnAnnotation ( ) .getAnEnumConstantArrayValue ( "method" ) .getName ( ) =
28
- [ "GET" , "HEAD" , "OPTIONS" , "TRACE" ]
28
+ this .getMethod ( ) = [ "GET" , "HEAD" , "OPTIONS" , "TRACE" ]
29
29
or
30
30
// If no request type is specified with `@RequestMapping`, then all request types
31
31
// are possible, so we treat this as unsafe; example: @RequestMapping(value = "test").
32
- not exists ( this .getAnAnnotation ( ) . getAnArrayValue ( "method" ) )
32
+ not exists ( this .getMethod ( ) )
33
33
)
34
34
}
35
35
}
@@ -49,12 +49,42 @@ private class StaplerCsrfUnprotectedMethod extends CsrfUnprotectedMethod instanc
49
49
}
50
50
}
51
51
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
+
52
83
/** 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 ( ) )
58
88
}
59
89
}
60
90
@@ -91,9 +121,9 @@ private class PreparedStatementDatabaseUpdateMethod extends DatabaseUpdateMethod
91
121
}
92
122
}
93
123
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 ( ) {
97
127
exists ( DataFlow:: Node n | this = n .asExpr ( ) .( Argument ) .getCall ( ) .getCallee ( ) |
98
128
sinkNode ( n , "sql-injection" ) and
99
129
// do not include `executeQuery` since it is typically used with a select statement
@@ -106,9 +136,10 @@ private class SqlInjectionMethod extends DatabaseUpdateMethod {
106
136
}
107
137
108
138
/**
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.
110
141
*/
111
- module SqlExecuteConfig implements DataFlow:: ConfigSig {
142
+ private module SqlExecuteConfig implements DataFlow:: ConfigSig {
112
143
predicate isSource ( DataFlow:: Node source ) {
113
144
exists ( StringLiteral sl | source .asExpr ( ) = sl |
114
145
sl .getValue ( ) .regexpMatch ( "^(?i)(insert|update|delete).*" )
@@ -117,16 +148,19 @@ module SqlExecuteConfig implements DataFlow::ConfigSig {
117
148
118
149
predicate isSink ( DataFlow:: Node sink ) {
119
150
exists ( Method m | m = sink .asExpr ( ) .( Argument ) .getCall ( ) .getCallee ( ) |
120
- m instanceof SqlInjectionMethod and
151
+ m instanceof SqlInjectionDatabaseUpdateMethod and
121
152
m .hasName ( "execute" )
122
153
)
123
154
}
124
155
}
125
156
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 > ;
128
162
129
- /** Provides classes and predicates representing call paths. */
163
+ /** Provides classes and predicates representing call graph paths. */
130
164
module CallGraph {
131
165
private newtype TCallPathNode =
132
166
TMethod ( Method m ) or
@@ -178,20 +212,19 @@ module CallGraph {
178
212
predicate edges ( CallPathNode pred , CallPathNode succ ) { pred .getASuccessor ( ) = succ }
179
213
}
180
214
181
- import CallGraph
182
-
183
215
/**
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.
186
218
*/
187
- predicate unprotectedDatabaseUpdate ( CallPathNode sourceMethod , CallPathNode sinkMethodCall ) {
219
+ private predicate unprotectedDatabaseUpdate ( CallPathNode sourceMethod , CallPathNode sinkMethodCall ) {
188
220
sourceMethod .asMethod ( ) instanceof CsrfUnprotectedMethod and
189
221
exists ( CallPathNode sinkMethod |
190
222
sinkMethod .asMethod ( ) instanceof DatabaseUpdateMethod and
191
223
sinkMethodCall .getASuccessor ( ) = pragma [ only_bind_into ] ( sinkMethod ) and
192
224
sourceMethod .getASuccessor + ( ) = pragma [ only_bind_into ] ( sinkMethodCall ) and
225
+ // exclude SQL `execute` calls that do not update database
193
226
if
194
- sinkMethod .asMethod ( ) instanceof SqlInjectionMethod and
227
+ sinkMethod .asMethod ( ) instanceof SqlInjectionDatabaseUpdateMethod and
195
228
sinkMethod .asMethod ( ) .hasName ( "execute" )
196
229
then
197
230
exists ( SqlExecuteFlow:: PathNode executeSink | SqlExecuteFlow:: flowPath ( _, executeSink ) |
@@ -202,22 +235,22 @@ predicate unprotectedDatabaseUpdate(CallPathNode sourceMethod, CallPathNode sink
202
235
}
203
236
204
237
/**
205
- * Holds if `src ` is an unprotected request handler that appears to
238
+ * Holds if `sourceMethod ` is an unprotected request handler that appears to
206
239
* change application state based on its name.
207
240
*/
208
- private predicate unprotectedHeuristicStateChange ( CallPathNode sourceMethod , CallPathNode sinkMethod ) {
241
+ private predicate unprotectedNameBasedStateChange ( CallPathNode sourceMethod , CallPathNode sinkMethod ) {
209
242
sourceMethod .asMethod ( ) instanceof CsrfUnprotectedMethod and
210
- sinkMethod .asMethod ( ) instanceof NameStateChangeMethod and
243
+ sinkMethod .asMethod ( ) instanceof NameBasedStateChangeMethod and
211
244
sinkMethod = sourceMethod and
212
245
// exclude any alerts that update a database
213
246
not unprotectedDatabaseUpdate ( sourceMethod , _)
214
247
}
215
248
216
249
/**
217
- * Holds if `src ` is an unprotected request handler that may
250
+ * Holds if `source ` is an unprotected request handler that may
218
251
* change an application's state.
219
252
*/
220
253
predicate unprotectedStateChange ( CallPathNode source , CallPathNode sink ) {
221
254
unprotectedDatabaseUpdate ( source , sink ) or
222
- unprotectedHeuristicStateChange ( source , sink )
255
+ unprotectedNameBasedStateChange ( source , sink )
223
256
}
0 commit comments