Skip to content

Commit b5389ef

Browse files
Jami CogswellJami Cogswell
authored andcommitted
Java: add name-based heuristic
1 parent 7836b27 commit b5389ef

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(".*(?i)(post|put|patch|delete|remove|create|add|update|edit|publish|unpublish|fill|move|transfer|log(out|in)|access|connect|register|submit|den(y|ied)).*") and
57+
not this.getName().regexpMatch("^(get|show|view|list|query|find).*")
58+
}
59+
}
60+
5261
/** A method that updates a database. */
5362
abstract class DatabaseUpdateMethod extends Method { }
5463

@@ -162,20 +171,46 @@ module CallGraph {
162171

163172
import CallGraph
164173

165-
/** Holds if `src` is an unprotected request handler that reaches a state-changing `sink`. */
166-
predicate unprotectedStateChange(CallPathNode src, CallPathNode sink, CallPathNode sinkPred) {
167-
src.asMethod() instanceof CsrfUnprotectedMethod and
168-
sink.asMethod() instanceof DatabaseUpdateMethod and
169-
sinkPred.getASuccessor() = sink and
170-
src.getASuccessor+() = sinkPred and
171-
if
172-
sink.asMethod() instanceof SqlInjectionMethod and
173-
sink.asMethod().hasName("execute")
174-
then
175-
exists(SqlExecuteFlow::PathNode executeSrc, SqlExecuteFlow::PathNode executeSink |
176-
SqlExecuteFlow::flowPath(executeSrc, executeSink)
177-
|
178-
sinkPred.asCall() = executeSink.getNode().asExpr().(Argument).getCall()
179-
)
180-
else any()
174+
/**
175+
* Holds if `src` is an unprotected request handler that reaches a
176+
* `sink` that updates a database.
177+
*/
178+
predicate unprotectedDatabaseUpdate(CallPathNode sourceMethod, CallPathNode sinkMethodCall) {
179+
sourceMethod.asMethod() instanceof CsrfUnprotectedMethod and
180+
exists(CallPathNode sinkMethod |
181+
sinkMethod.asMethod() instanceof DatabaseUpdateMethod and
182+
sinkMethodCall.getASuccessor() = sinkMethod and
183+
sourceMethod.getASuccessor+() = sinkMethodCall and
184+
if
185+
sinkMethod.asMethod() instanceof SqlInjectionMethod and
186+
sinkMethod.asMethod().hasName("execute")
187+
then
188+
exists(SqlExecuteFlow::PathNode executeSrc, SqlExecuteFlow::PathNode executeSink |
189+
SqlExecuteFlow::flowPath(executeSrc, executeSink)
190+
|
191+
sinkMethodCall.asCall() = executeSink.getNode().asExpr().(Argument).getCall()
192+
)
193+
else any()
194+
)
195+
}
196+
197+
/**
198+
* Holds if `src` is an unprotected request handler that appears to
199+
* change application state based on its name.
200+
*/
201+
private predicate unprotectedHeuristicStateChange(CallPathNode sourceMethod, CallPathNode sinkMethod) {
202+
sourceMethod.asMethod() instanceof CsrfUnprotectedMethod and
203+
sinkMethod.asMethod() instanceof NameStateChangeMethod and
204+
sinkMethod = sourceMethod and
205+
// exclude any alerts that update a database
206+
not unprotectedDatabaseUpdate(sourceMethod, _)
207+
}
208+
209+
/**
210+
* Holds if `src` is an unprotected request handler that may
211+
* change an application's state.
212+
*/
213+
predicate unprotectedStateChange(CallPathNode source, CallPathNode sink) {
214+
unprotectedDatabaseUpdate(source, sink) or
215+
unprotectedHeuristicStateChange(source, sink)
181216
}

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)