mirror of
https://github.com/github/codeql.git
synced 2026-04-27 09:45:15 +02:00
Added query for Jakarta EL injections
- Added JakartaExpressionInjection.ql - Added a qhelp file with examples
This commit is contained in:
@@ -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()
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
@@ -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") }
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user