Skip to content

Commit 079769e

Browse files
committed
Refactored SpelInjection.qll to use CSV sink models
1 parent fc6af04 commit 079769e

File tree

4 files changed

+163
-210
lines changed

4 files changed

+163
-210
lines changed

java/ql/src/Security/CWE/CWE-094/SpelInjection.ql

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,25 @@
1111
*/
1212

1313
import java
14-
import SpelInjectionLib
14+
import semmle.code.java.security.SpelInjection
1515
import DataFlow::PathGraph
1616

17-
from DataFlow::PathNode source, DataFlow::PathNode sink, ExpressionInjectionConfig conf
17+
/**
18+
* A taint-tracking configuration for unsafe user input
19+
* that is used to construct and evaluate a SpEL expression.
20+
*/
21+
class SpELInjectionConfig extends TaintTracking::Configuration {
22+
SpELInjectionConfig() { this = "SpELInjectionConfig" }
23+
24+
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
25+
26+
override predicate isSink(DataFlow::Node sink) { sink instanceof SpelExpressionEvaluationSink }
27+
28+
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
29+
any(SpelExpressionInjectionAdditionalTaintStep c).step(node1, node2)
30+
}
31+
}
32+
33+
from DataFlow::PathNode source, DataFlow::PathNode sink, SpELInjectionConfig conf
1834
where conf.hasFlowPath(source, sink)
1935
select sink.getNode(), source, sink, "SpEL injection from $@.", source.getNode(), "this user input"

java/ql/src/Security/CWE/CWE-094/SpelInjectionLib.qll

Lines changed: 0 additions & 97 deletions
This file was deleted.

java/ql/src/Security/CWE/CWE-094/SpringFrameworkLib.qll

Lines changed: 0 additions & 111 deletions
This file was deleted.
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/** Provides classes to reason about SpEL injection attacks. */
2+
3+
import java
4+
import semmle.code.java.dataflow.DataFlow
5+
import semmle.code.java.dataflow.ExternalFlow
6+
import semmle.code.java.dataflow.FlowSources
7+
8+
/** A data flow sink for unvalidated user input that is used to construct SpEL expressions. */
9+
abstract class SpelExpressionEvaluationSink extends DataFlow::ExprNode { }
10+
11+
private class SpelExpressionEvaluationModel extends SinkModelCsv {
12+
override predicate row(string row) {
13+
row =
14+
[
15+
"org.springframework.expression;Expression;true;getValue;;;Argument[-1];spel",
16+
"org.springframework.expression;Expression;true;getValueTypeDescriptor;;;Argument[-1];spel",
17+
"org.springframework.expression;Expression;true;getValueType;;;Argument[-1];spel",
18+
"org.springframework.expression;Expression;true;setValue;;;Argument[-1];spel"
19+
]
20+
}
21+
}
22+
23+
/** Default sink for SpEL injection vulnerabilities. */
24+
private class DefaultSpelExpressionEvaluationSink extends SpelExpressionEvaluationSink {
25+
DefaultSpelExpressionEvaluationSink() {
26+
exists(MethodAccess ma |
27+
sinkNode(this, "spel") and
28+
this.asExpr() = ma.getQualifier() and
29+
not exists(SafeEvaluationContextFlowConfig config |
30+
config.hasFlowTo(DataFlow::exprNode(ma.getArgument(0)))
31+
)
32+
)
33+
}
34+
}
35+
36+
/**
37+
* A unit class for adding additional taint steps.
38+
*
39+
* Extend this class to add additional taint steps that should apply to the `SpELInjectionConfig`.
40+
*/
41+
class SpelExpressionInjectionAdditionalTaintStep extends Unit {
42+
/**
43+
* Holds if the step from `node1` to `node2` should be considered a taint
44+
* step for the `SpELInjectionConfig` configuration.
45+
*/
46+
abstract predicate step(DataFlow::Node node1, DataFlow::Node node2);
47+
}
48+
49+
/** A set of additional taint steps to consider when taint tracking SpEL related data flows. */
50+
private class DefaultSpelExpressionInjectionAdditionalTaintStep extends SpelExpressionInjectionAdditionalTaintStep {
51+
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
52+
expressionParsingStep(node1, node2)
53+
}
54+
}
55+
56+
/**
57+
* A configuration for safe evaluation context that may be used in expression evaluation.
58+
*/
59+
class SafeEvaluationContextFlowConfig extends DataFlow2::Configuration {
60+
SafeEvaluationContextFlowConfig() { this = "SpelInjection::SafeEvaluationContextFlowConfig" }
61+
62+
override predicate isSource(DataFlow::Node source) { source instanceof SafeContextSource }
63+
64+
override predicate isSink(DataFlow::Node sink) {
65+
exists(MethodAccess ma |
66+
ma.getMethod() instanceof ExpressionEvaluationMethod and
67+
ma.getArgument(0) = sink.asExpr()
68+
)
69+
}
70+
71+
override int fieldFlowBranchLimit() { result = 0 }
72+
}
73+
74+
private class SafeContextSource extends DataFlow::ExprNode {
75+
SafeContextSource() {
76+
isSimpleEvaluationContextConstructorCall(getExpr()) or
77+
isSimpleEvaluationContextBuilderCall(getExpr())
78+
}
79+
}
80+
81+
/**
82+
* Holds if `expr` constructs `SimpleEvaluationContext`.
83+
*/
84+
private predicate isSimpleEvaluationContextConstructorCall(Expr expr) {
85+
exists(ConstructorCall cc |
86+
cc.getConstructedType() instanceof SimpleEvaluationContext and
87+
cc = expr
88+
)
89+
}
90+
91+
/**
92+
* Holds if `expr` builds `SimpleEvaluationContext` via `SimpleEvaluationContext.Builder`,
93+
* for instance, `SimpleEvaluationContext.forReadWriteDataBinding().build()`.
94+
*/
95+
private predicate isSimpleEvaluationContextBuilderCall(Expr expr) {
96+
exists(MethodAccess ma, Method m | ma.getMethod() = m |
97+
m.getDeclaringType() instanceof SimpleEvaluationContextBuilder and
98+
m.hasName("build") and
99+
ma = expr
100+
)
101+
}
102+
103+
/**
104+
* Methods that trigger evaluation of an expression.
105+
*/
106+
private class ExpressionEvaluationMethod extends Method {
107+
ExpressionEvaluationMethod() {
108+
this.getDeclaringType() instanceof Expression and
109+
this.hasName(["getValue", "getValueTypeDescriptor", "getValueType", "setValue"])
110+
}
111+
}
112+
113+
/**
114+
* Holds if `node1` to `node2` is a dataflow step that parses a SpEL expression,
115+
* by calling `parser.parseExpression(tainted)`.
116+
*/
117+
private predicate expressionParsingStep(DataFlow::Node node1, DataFlow::Node node2) {
118+
exists(MethodAccess ma, Method m | ma.getMethod() = m |
119+
m.getDeclaringType().getAnAncestor*() instanceof ExpressionParser and
120+
m.hasName("parseExpression") and
121+
ma.getAnArgument() = node1.asExpr() and
122+
node2.asExpr() = ma
123+
)
124+
}
125+
126+
private class SimpleEvaluationContext extends RefType {
127+
SimpleEvaluationContext() {
128+
hasQualifiedName("org.springframework.expression.spel.support", "SimpleEvaluationContext")
129+
}
130+
}
131+
132+
private class SimpleEvaluationContextBuilder extends RefType {
133+
SimpleEvaluationContextBuilder() {
134+
hasQualifiedName("org.springframework.expression.spel.support",
135+
"SimpleEvaluationContext$Builder")
136+
}
137+
}
138+
139+
private class Expression extends RefType {
140+
Expression() { hasQualifiedName("org.springframework.expression", "Expression") }
141+
}
142+
143+
private class ExpressionParser extends RefType {
144+
ExpressionParser() { hasQualifiedName("org.springframework.expression", "ExpressionParser") }
145+
}

0 commit comments

Comments
 (0)