Added query for Jakarta EL injections

- Added JakartaExpressionInjection.ql
- Added a qhelp file with examples
This commit is contained in:
Artem Smotrakov
2021-02-02 17:37:14 +01:00
parent 07ca09ef90
commit 73e940de74
7 changed files with 213 additions and 12 deletions

View File

@@ -0,0 +1,14 @@
import java
import semmle.code.java.dataflow.FlowSources
/**
* Holds if `fromNode` to `toNode` is a dataflow step that returns data from
* a bean by calling one of its getters.
*/
predicate returnsDataFromBean(DataFlow::Node fromNode, DataFlow::Node toNode) {
exists(MethodAccess ma, Method m | ma.getMethod() = m |
m instanceof GetterMethod and
ma.getQualifier() = fromNode.asExpr() and
ma = toNode.asExpr()
)
}

View File

@@ -0,0 +1,65 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
Jakarta Expression Language (EL) is an expression language for Java applications.
There are a single language specification and multiple implementations
such as Glassfish, Juel, Apache Commons EL, etc.
The language allows invocation of methods available in the JVM.
If an expression is built using attacker-controlled data,
and then evaluated, then it may allow the attacker to run arbitrary code.
</p>
</overview>
<recommendation>
<p>
It is generally recommended to avoid using untrusted data in an EL expression.
Before using untrusted data to build an EL expressoin, the data should be validated
to ensure it is not evaluated as expression language. If the EL implementaion offers
configuring a sandbox for EL expression, they should be run in a restircitive sandbox
that allows accessing only explicitly allowed classes. If the EL implementation
does not allow sandboxing, consider using other expressiong language implementations
with sandboxing capabilities such as Apache Commons JEXL or the Spring Expression Language.
</p>
</recommendation>
<example>
<p>
The following example shows how untrusted data is used to build and run an expression
using the JUEL interpreter:
</p>
<sample src="UnsafeExpressionEvaluationWithJUEL.java" />
<p>
JUEL does not allow to run expression in a sandbox. To prevent running arbitrary code,
incoming data has to be checked before including to an expression. The next example
uses a Regex pattern to check whether a user tries to run an allowed exression or not:
</p>
<sample src="SaferExpressionEvaluationWithJUEL.java" />
</example>
<references>
<li>
Eclipse Foundation:
<a href="https://projects.eclipse.org/projects/ee4j.el">Jakarta Expression Language</a>.
</li>
<li>
Jakarta EE documentation:
<a href="https://javadoc.io/doc/jakarta.el/jakarta.el-api/latest/index.html">Jakarta Expression Language API</a>
</li>
<li>
OWASP:
<a href="https://owasp.org/www-community/vulnerabilities/Expression_Language_Injection">Expression Language Injection</a>.
</li>
<li>
JUEL:
<a href="http://juel.sourceforge.net">Home page</a>
</li>
<li>
Apache Foundation:
<a href="https://commons.apache.org/dormant/commons-el/">Apache Commons EL</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,20 @@
/**
* @name Java EE Expression Language injection
* @description Evaluation of a user-controlled Jave EE expression
* may lead to arbitrary code execution.
* @kind path-problem
* @problem.severity error
* @precision high
* @id java/javaee-expression-injection
* @tags security
* external/cwe/cwe-094
*/
import java
import JavaEEExpressionInjectionLib
import DataFlow::PathGraph
from DataFlow::PathNode source, DataFlow::PathNode sink, JavaEEExpressionInjectionConfig conf
where conf.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Java EE Expression Language injection from $@.",
source.getNode(), "this user input"

View File

@@ -0,0 +1,98 @@
import java
import InjectionLib
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
/**
* A taint-tracking configuration for unsafe user input
* that is used to construct and evaluate a Java EE expression.
*/
class JavaEEExpressionInjectionConfig extends TaintTracking::Configuration {
JavaEEExpressionInjectionConfig() { this = "JavaEEExpressionInjectionConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof ExpressionEvaluationSink }
override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
any(TaintPropagatingCall c).taintFlow(fromNode, toNode) or
returnsDataFromBean(fromNode, toNode)
}
}
/**
* A sink for Expresssion Language injection vulnerabilities,
* i.e. method calls that run evaluation of a Java EE expression.
*/
private class ExpressionEvaluationSink extends DataFlow::ExprNode {
ExpressionEvaluationSink() {
exists(MethodAccess ma, Method m, Expr taintFrom |
ma.getMethod() = m and taintFrom = this.asExpr()
|
m.getDeclaringType() instanceof ValueExpression and
m.hasName(["getValue", "setValue"]) and
ma.getQualifier() = taintFrom
or
m.getDeclaringType() instanceof MethodExpression and
m.hasName("invoke") and
ma.getQualifier() = taintFrom
or
m.getDeclaringType() instanceof LambdaExpression and
m.hasName("invoke") and
ma.getQualifier() = taintFrom
or
m.getDeclaringType() instanceof ELProcessor and
m.hasName(["eval", "getValue", "setValue"]) and
ma.getArgument(0) = taintFrom
)
}
}
/**
* Defines method calls that propagate tainted expressions.
*/
private class TaintPropagatingCall extends Call {
Expr taintFromExpr;
TaintPropagatingCall() {
taintFromExpr = this.getArgument(1) and
exists(Method m | this.(MethodAccess).getMethod() = m |
m.getDeclaringType() instanceof ExpressionFactory and
m.hasName(["createValueExpression", "createMethodExpression"]) and
taintFromExpr.getType() instanceof TypeString
)
or
exists(Constructor c | this.(ConstructorCall).getConstructor() = c |
c.getDeclaringType() instanceof LambdaExpression and
taintFromExpr.getType() instanceof ValueExpression
)
}
/**
* Holds if `fromNode` to `toNode` is a dataflow step that propagates
* tainted data.
*/
predicate taintFlow(DataFlow::Node fromNode, DataFlow::Node toNode) {
fromNode.asExpr() = taintFromExpr and toNode.asExpr() = this
}
}
private class ELProcessor extends RefType {
ELProcessor() { hasQualifiedName("javax.el", "ELProcessor") }
}
private class ExpressionFactory extends RefType {
ExpressionFactory() { hasQualifiedName("javax.el", "ExpressionFactory") }
}
private class ValueExpression extends RefType {
ValueExpression() { hasQualifiedName("javax.el", "ValueExpression") }
}
private class MethodExpression extends RefType {
MethodExpression() { hasQualifiedName("javax.el", "MethodExpression") }
}
private class LambdaExpression extends RefType {
LambdaExpression() { hasQualifiedName("javax.el", "LambdaExpression") }
}

View File

@@ -1,4 +1,5 @@
import java
import InjectionLib
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
@@ -152,18 +153,6 @@ private predicate createsJexlEngine(DataFlow::Node fromNode, DataFlow::Node toNo
)
}
/**
* Holds if `fromNode` to `toNode` is a dataflow step that returns data from
* a bean by calling one of its getters.
*/
private predicate returnsDataFromBean(DataFlow::Node fromNode, DataFlow::Node toNode) {
exists(MethodAccess ma, Method m | ma.getMethod() = m |
m instanceof GetterMethod and
ma.getQualifier() = fromNode.asExpr() and
ma = toNode.asExpr()
)
}
/**
* A methods in the `JexlEngine` class that gets or sets a property with a JEXL expression.
*/

View File

@@ -0,0 +1,10 @@
String input = getRemoteUserInput();
String pattern = "(inside|outside)\\.(temperature|humidity)";
if (!input.matches(pattern)) {
throw new IllegalArgumentException("Unexpected exression");
}
String expression = "${" + input + "}";
ExpressionFactory factory = new de.odysseus.el.ExpressionFactoryImpl();
ValueExpression e = factory.createValueExpression(context, expression, Object.class);
SimpleContext context = getContext();
Object result = e.getValue(context);

View File

@@ -0,0 +1,5 @@
String expression = "${" + getRemoteUserInput() + "}";
ExpressionFactory factory = new de.odysseus.el.ExpressionFactoryImpl();
ValueExpression e = factory.createValueExpression(context, expression, Object.class);
SimpleContext context = getContext();
Object result = e.getValue(context);