Skip to content

Commit 73e940d

Browse files
Added query for Jakarta EL injections
- Added JakartaExpressionInjection.ql - Added a qhelp file with examples
1 parent 07ca09e commit 73e940d

7 files changed

+213
-12
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import java
2+
import semmle.code.java.dataflow.FlowSources
3+
4+
/**
5+
* Holds if `fromNode` to `toNode` is a dataflow step that returns data from
6+
* a bean by calling one of its getters.
7+
*/
8+
predicate returnsDataFromBean(DataFlow::Node fromNode, DataFlow::Node toNode) {
9+
exists(MethodAccess ma, Method m | ma.getMethod() = m |
10+
m instanceof GetterMethod and
11+
ma.getQualifier() = fromNode.asExpr() and
12+
ma = toNode.asExpr()
13+
)
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
4+
<overview>
5+
<p>
6+
Jakarta Expression Language (EL) is an expression language for Java applications.
7+
There are a single language specification and multiple implementations
8+
such as Glassfish, Juel, Apache Commons EL, etc.
9+
The language allows invocation of methods available in the JVM.
10+
If an expression is built using attacker-controlled data,
11+
and then evaluated, then it may allow the attacker to run arbitrary code.
12+
</p>
13+
</overview>
14+
15+
<recommendation>
16+
<p>
17+
It is generally recommended to avoid using untrusted data in an EL expression.
18+
Before using untrusted data to build an EL expressoin, the data should be validated
19+
to ensure it is not evaluated as expression language. If the EL implementaion offers
20+
configuring a sandbox for EL expression, they should be run in a restircitive sandbox
21+
that allows accessing only explicitly allowed classes. If the EL implementation
22+
does not allow sandboxing, consider using other expressiong language implementations
23+
with sandboxing capabilities such as Apache Commons JEXL or the Spring Expression Language.
24+
</p>
25+
</recommendation>
26+
27+
<example>
28+
<p>
29+
The following example shows how untrusted data is used to build and run an expression
30+
using the JUEL interpreter:
31+
</p>
32+
<sample src="UnsafeExpressionEvaluationWithJUEL.java" />
33+
34+
<p>
35+
JUEL does not allow to run expression in a sandbox. To prevent running arbitrary code,
36+
incoming data has to be checked before including to an expression. The next example
37+
uses a Regex pattern to check whether a user tries to run an allowed exression or not:
38+
</p>
39+
<sample src="SaferExpressionEvaluationWithJUEL.java" />
40+
41+
</example>
42+
43+
<references>
44+
<li>
45+
Eclipse Foundation:
46+
<a href="https://projects.eclipse.org/projects/ee4j.el">Jakarta Expression Language</a>.
47+
</li>
48+
<li>
49+
Jakarta EE documentation:
50+
<a href="https://javadoc.io/doc/jakarta.el/jakarta.el-api/latest/index.html">Jakarta Expression Language API</a>
51+
</li>
52+
<li>
53+
OWASP:
54+
<a href="https://owasp.org/www-community/vulnerabilities/Expression_Language_Injection">Expression Language Injection</a>.
55+
</li>
56+
<li>
57+
JUEL:
58+
<a href="http://juel.sourceforge.net">Home page</a>
59+
</li>
60+
<li>
61+
Apache Foundation:
62+
<a href="https://commons.apache.org/dormant/commons-el/">Apache Commons EL</a>
63+
</li>
64+
</references>
65+
</qhelp>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* @name Java EE Expression Language injection
3+
* @description Evaluation of a user-controlled Jave EE expression
4+
* may lead to arbitrary code execution.
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @precision high
8+
* @id java/javaee-expression-injection
9+
* @tags security
10+
* external/cwe/cwe-094
11+
*/
12+
13+
import java
14+
import JavaEEExpressionInjectionLib
15+
import DataFlow::PathGraph
16+
17+
from DataFlow::PathNode source, DataFlow::PathNode sink, JavaEEExpressionInjectionConfig conf
18+
where conf.hasFlowPath(source, sink)
19+
select sink.getNode(), source, sink, "Java EE Expression Language injection from $@.",
20+
source.getNode(), "this user input"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import java
2+
import InjectionLib
3+
import semmle.code.java.dataflow.FlowSources
4+
import semmle.code.java.dataflow.TaintTracking
5+
6+
/**
7+
* A taint-tracking configuration for unsafe user input
8+
* that is used to construct and evaluate a Java EE expression.
9+
*/
10+
class JavaEEExpressionInjectionConfig extends TaintTracking::Configuration {
11+
JavaEEExpressionInjectionConfig() { this = "JavaEEExpressionInjectionConfig" }
12+
13+
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
14+
15+
override predicate isSink(DataFlow::Node sink) { sink instanceof ExpressionEvaluationSink }
16+
17+
override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
18+
any(TaintPropagatingCall c).taintFlow(fromNode, toNode) or
19+
returnsDataFromBean(fromNode, toNode)
20+
}
21+
}
22+
23+
/**
24+
* A sink for Expresssion Language injection vulnerabilities,
25+
* i.e. method calls that run evaluation of a Java EE expression.
26+
*/
27+
private class ExpressionEvaluationSink extends DataFlow::ExprNode {
28+
ExpressionEvaluationSink() {
29+
exists(MethodAccess ma, Method m, Expr taintFrom |
30+
ma.getMethod() = m and taintFrom = this.asExpr()
31+
|
32+
m.getDeclaringType() instanceof ValueExpression and
33+
m.hasName(["getValue", "setValue"]) and
34+
ma.getQualifier() = taintFrom
35+
or
36+
m.getDeclaringType() instanceof MethodExpression and
37+
m.hasName("invoke") and
38+
ma.getQualifier() = taintFrom
39+
or
40+
m.getDeclaringType() instanceof LambdaExpression and
41+
m.hasName("invoke") and
42+
ma.getQualifier() = taintFrom
43+
or
44+
m.getDeclaringType() instanceof ELProcessor and
45+
m.hasName(["eval", "getValue", "setValue"]) and
46+
ma.getArgument(0) = taintFrom
47+
)
48+
}
49+
}
50+
51+
/**
52+
* Defines method calls that propagate tainted expressions.
53+
*/
54+
private class TaintPropagatingCall extends Call {
55+
Expr taintFromExpr;
56+
57+
TaintPropagatingCall() {
58+
taintFromExpr = this.getArgument(1) and
59+
exists(Method m | this.(MethodAccess).getMethod() = m |
60+
m.getDeclaringType() instanceof ExpressionFactory and
61+
m.hasName(["createValueExpression", "createMethodExpression"]) and
62+
taintFromExpr.getType() instanceof TypeString
63+
)
64+
or
65+
exists(Constructor c | this.(ConstructorCall).getConstructor() = c |
66+
c.getDeclaringType() instanceof LambdaExpression and
67+
taintFromExpr.getType() instanceof ValueExpression
68+
)
69+
}
70+
71+
/**
72+
* Holds if `fromNode` to `toNode` is a dataflow step that propagates
73+
* tainted data.
74+
*/
75+
predicate taintFlow(DataFlow::Node fromNode, DataFlow::Node toNode) {
76+
fromNode.asExpr() = taintFromExpr and toNode.asExpr() = this
77+
}
78+
}
79+
80+
private class ELProcessor extends RefType {
81+
ELProcessor() { hasQualifiedName("javax.el", "ELProcessor") }
82+
}
83+
84+
private class ExpressionFactory extends RefType {
85+
ExpressionFactory() { hasQualifiedName("javax.el", "ExpressionFactory") }
86+
}
87+
88+
private class ValueExpression extends RefType {
89+
ValueExpression() { hasQualifiedName("javax.el", "ValueExpression") }
90+
}
91+
92+
private class MethodExpression extends RefType {
93+
MethodExpression() { hasQualifiedName("javax.el", "MethodExpression") }
94+
}
95+
96+
private class LambdaExpression extends RefType {
97+
LambdaExpression() { hasQualifiedName("javax.el", "LambdaExpression") }
98+
}

java/ql/src/experimental/Security/CWE/CWE-094/JexlInjectionLib.qll

+1-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import java
2+
import InjectionLib
23
import semmle.code.java.dataflow.FlowSources
34
import semmle.code.java.dataflow.TaintTracking
45

@@ -152,18 +153,6 @@ private predicate createsJexlEngine(DataFlow::Node fromNode, DataFlow::Node toNo
152153
)
153154
}
154155

155-
/**
156-
* Holds if `fromNode` to `toNode` is a dataflow step that returns data from
157-
* a bean by calling one of its getters.
158-
*/
159-
private predicate returnsDataFromBean(DataFlow::Node fromNode, DataFlow::Node toNode) {
160-
exists(MethodAccess ma, Method m | ma.getMethod() = m |
161-
m instanceof GetterMethod and
162-
ma.getQualifier() = fromNode.asExpr() and
163-
ma = toNode.asExpr()
164-
)
165-
}
166-
167156
/**
168157
* A methods in the `JexlEngine` class that gets or sets a property with a JEXL expression.
169158
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
String input = getRemoteUserInput();
2+
String pattern = "(inside|outside)\\.(temperature|humidity)";
3+
if (!input.matches(pattern)) {
4+
throw new IllegalArgumentException("Unexpected exression");
5+
}
6+
String expression = "${" + input + "}";
7+
ExpressionFactory factory = new de.odysseus.el.ExpressionFactoryImpl();
8+
ValueExpression e = factory.createValueExpression(context, expression, Object.class);
9+
SimpleContext context = getContext();
10+
Object result = e.getValue(context);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
String expression = "${" + getRemoteUserInput() + "}";
2+
ExpressionFactory factory = new de.odysseus.el.ExpressionFactoryImpl();
3+
ValueExpression e = factory.createValueExpression(context, expression, Object.class);
4+
SimpleContext context = getContext();
5+
Object result = e.getValue(context);

0 commit comments

Comments
 (0)