Skip to content

Commit 39ccde0

Browse files
Jami CogswellJami Cogswell
authored andcommitted
Java: add name-based heuristic
1 parent 286c655 commit 39ccde0

File tree

4 files changed

+62
-23
lines changed

4 files changed

+62
-23
lines changed

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

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -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("(^|\\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*")
58+
}
59+
}
60+
5261
/** A method that updates a database. */
5362
abstract class DatabaseUpdateMethod extends Method { }
5463

@@ -163,20 +172,46 @@ module CallGraph {
163172

164173
import 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
}

java/ql/src/Security/CWE/CWE-352/CsrfUnprotectedRequestType.ql

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ import semmle.code.java.security.CsrfUnprotectedRequestTypeQuery
1717

1818
query predicate edges(CallPathNode pred, CallPathNode succ) { CallGraph::edges(pred, succ) }
1919

20-
from CallPathNode source, CallPathNode reachable, CallPathNode callsReachable
21-
where unprotectedStateChange(source, reachable, callsReachable)
22-
select source.asMethod(), source, callsReachable,
20+
from CallPathNode source, CallPathNode sink
21+
where unprotectedStateChange(source, sink)
22+
select source.asMethod(), source, sink,
2323
"Potential CSRF vulnerability due to using an HTTP request type which is not default-protected from CSRF for an apparent $@.",
24-
callsReachable, "state-changing action"
24+
sink, "state-changing action"

java/ql/test/query-tests/security/CWE-352/CsrfUnprotectedRequestTypeTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,4 +211,10 @@ public void bad9(@RequestParam String user) { // $ hasCsrfUnprotectedRequestType
211211
public void bad10(@RequestParam String user) { // $ hasCsrfUnprotectedRequestType
212212
myBatisService.bad10(user);
213213
}
214+
215+
// BAD: method name implies a state-change
216+
@GetMapping(value = "delete")
217+
public String delete(@RequestParam String user) { // $ hasCsrfUnprotectedRequestType
218+
return "delete";
219+
}
214220
}

java/ql/test/query-tests/security/CWE-352/CsrfUnprotectedRequestTypeTest.ql

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@ module CsrfUnprotectedRequestTypeTest implements TestSig {
77

88
predicate hasActualResult(Location location, string element, string tag, string value) {
99
tag = "hasCsrfUnprotectedRequestType" and
10-
exists(CallPathNode src, CallPathNode sink, CallPathNode sinkPred |
11-
unprotectedStateChange(src, sink, sinkPred)
12-
|
10+
exists(CallPathNode src, CallPathNode sink | unprotectedStateChange(src, sink) |
1311
src.getLocation() = location and
1412
element = src.toString() and
1513
value = ""

0 commit comments

Comments
 (0)