Skip to content

Commit b56a8d8

Browse files
Jami CogswellJami Cogswell
Jami Cogswell
authored and
Jami Cogswell
committed
Java: some clean-up and refactoring
1 parent b2d84c6 commit b56a8d8

File tree

2 files changed

+66
-28
lines changed

2 files changed

+66
-28
lines changed

java/ql/lib/semmle/code/java/frameworks/spring/SpringController.qll

+5
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,11 @@ class SpringRequestMappingMethod extends SpringControllerMethod {
156156
/** Gets the "value" @RequestMapping annotation value, if present. */
157157
string getValue() { result = requestMappingAnnotation.getStringValue("value") }
158158

159+
/** Gets the "method" @RequestMapping annotation value, if present. */
160+
string getMethod() {
161+
result = requestMappingAnnotation.getAnEnumConstantArrayValue("method").getName()
162+
}
163+
159164
/** Holds if this is considered an `@ResponseBody` method. */
160165
predicate isResponseBody() {
161166
this.getAnAnnotation().getType() instanceof SpringResponseBodyAnnotationType or

java/ql/lib/semmle/code/java/security/CsrfUnprotectedRequestTypeQuery.qll

+61-28
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ private import semmle.code.java.frameworks.Jdbc
88
private import semmle.code.java.dataflow.ExternalFlow
99
private import semmle.code.java.dispatch.VirtualDispatch
1010
private import semmle.code.java.dataflow.TaintTracking
11+
import CallGraph
1112

1213
/** A method that is not protected from CSRF by default. */
1314
abstract 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. */
130164
module 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
*/
220253
predicate unprotectedStateChange(CallPathNode source, CallPathNode sink) {
221254
unprotectedDatabaseUpdate(source, sink) or
222-
unprotectedHeuristicStateChange(source, sink)
255+
unprotectedNameBasedStateChange(source, sink)
223256
}

0 commit comments

Comments
 (0)