Moved from experimental

This commit is contained in:
Tony Torralba
2021-05-03 13:15:24 +02:00
parent 38e052482c
commit 4bfd34b1fe
12 changed files with 7 additions and 6 deletions

View File

@@ -0,0 +1,63 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
Java EXpression Language (JEXL) is a simple expression language
provided by the Apache Commons JEXL library.
The syntax is close to a mix of ECMAScript and shell-script.
The language allows invocation of methods available in the JVM.
If a JEXL 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 input in a JEXL expression.
If it is not possible, JEXL expressions should be run in a sandbox that allows accessing only
explicitly allowed classes.
</p>
</recommendation>
<example>
<p>
The following example uses untrusted data to build and run a JEXL expression.
</p>
<sample src="UnsafeJexlExpressionEvaluation.java" />
<p>
The next example shows how an untrusted JEXL expression can be run
in a sandbox that allows accessing only methods in the <code>java.lang.Math</code> class.
The sandbox is implemented using <code>JexlSandbox</code> class that is provided by
Apache Commons JEXL 3.
</p>
<sample src="SaferJexlExpressionEvaluationWithSandbox.java" />
<p>
The next example shows another way how a sandbox can be implemented.
It uses a custom implementation of <code>JexlUberspect</code>
that checks if callees are instances of allowed classes.
</p>
<sample src="SaferJexlExpressionEvaluationWithUberspectSandbox.java" />
</example>
<references>
<li>
Apache Commons JEXL:
<a href="https://commons.apache.org/proper/commons-jexl/">Project page</a>.
</li>
<li>
Apache Commons JEXL documentation:
<a href="https://commons.apache.org/proper/commons-jexl/javadocs/apidocs-2.1.1/">JEXL 2.1.1 API</a>.
</li>
<li>
Apache Commons JEXL documentation:
<a href="https://commons.apache.org/proper/commons-jexl/apidocs/index.html">JEXL 3.1 API</a>.
</li>
<li>
OWASP:
<a href="https://owasp.org/www-community/vulnerabilities/Expression_Language_Injection">Expression Language Injection</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,39 @@
/**
* @name Expression language injection (JEXL)
* @description Evaluation of a user-controlled JEXL expression
* may lead to arbitrary code execution.
* @kind path-problem
* @problem.severity error
* @precision high
* @id java/jexl-expression-injection
* @tags security
* external/cwe/cwe-094
*/
import java
import JexlInjectionLib
import DataFlow::PathGraph
import semmle.code.java.dataflow.FlowSources
//import FlowUtils
/**
* A taint-tracking configuration for unsafe user input
* that is used to construct and evaluate a JEXL expression.
* It supports both JEXL 2 and 3.
*/
class JexlInjectionConfig extends TaintTracking::Configuration {
JexlInjectionConfig() { this = "JexlInjectionConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof JexlEvaluationSink }
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
any(JexlInjectionAdditionalTaintStep c).step(node1, node2) /*or
hasGetterFlow(node1, node2)*/
}
}
from DataFlow::PathNode source, DataFlow::PathNode sink, JexlInjectionConfig conf
where conf.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "JEXL injection from $@.", source.getNode(), "this user input"

View File

@@ -0,0 +1,241 @@
import java
import semmle.code.java.dataflow.TaintTracking
private import semmle.code.java.dataflow.ExternalFlow
/**
* A sink for Expresssion Language injection vulnerabilities via Jexl,
* i.e. method calls that run evaluation of a JEXL expression.
*/
abstract class JexlEvaluationSink extends DataFlow::ExprNode { }
/** Default sink for JXEL injection vulnerabilities. */
private class DefaultJexlEvaluationSink extends JexlEvaluationSink {
DefaultJexlEvaluationSink() { sinkNode(this, "jexl") }
}
private class DefaultJexlInjectionSinkModel extends SinkModelCsv {
override predicate row(string row) {
row =
[
// JEXL2
"org.apache.commons.jexl2;JexlEngine;false;getProperty;(JexlContext,Object,String);;Argument[2];jexl",
"org.apache.commons.jexl2;JexlEngine;false;getProperty;(Object,String);;Argument[1];jexl",
"org.apache.commons.jexl2;JexlEngine;false;setProperty;(JexlContext,Object,String,Object);;Argument[2];jexl",
"org.apache.commons.jexl2;JexlEngine;false;setProperty;(Object,String,Object);;Argument[1];jexl",
"org.apache.commons.jexl2;Expression;false;evaluate;;;Argument[-1];jexl",
"org.apache.commons.jexl2;Expression;false;callable;;;Argument[-1];jexl",
"org.apache.commons.jexl2;JexlExpression;false;evaluate;;;Argument[-1];jexl",
"org.apache.commons.jexl2;JexlExpression;false;callable;;;Argument[-1];jexl",
"org.apache.commons.jexl2;Script;false;execute;;;Argument[-1];jexl",
"org.apache.commons.jexl2;Script;false;callable;;;Argument[-1];jexl",
"org.apache.commons.jexl2;JexlScript;false;execute;;;Argument[-1];jexl",
"org.apache.commons.jexl2;JexlScript;false;callable;;;Argument[-1];jexl",
"org.apache.commons.jexl2;UnifiedJEXL$Expression;false;evaluate;;;Argument[-1];jexl",
"org.apache.commons.jexl2;UnifiedJEXL$Expression;false;prepare;;;Argument[-1];jexl",
"org.apache.commons.jexl2;UnifiedJEXL$Template;false;evaluate;;;Argument[-1];jexl",
// JEXL3
"org.apache.commons.jexl3;JexlEngine;false;getProperty;(JexlContext,Object,String);;Argument[2];jexl",
"org.apache.commons.jexl3;JexlEngine;false;getProperty;(Object,String);;Argument[1];jexl",
"org.apache.commons.jexl3;JexlEngine;false;setProperty;(JexlContext,Object,String);;Argument[2];jexl",
"org.apache.commons.jexl3;JexlEngine;false;setProperty;(Object,String,Object);;Argument[1];jexl",
"org.apache.commons.jexl3;Expression;false;evaluate;;;Argument[-1];jexl",
"org.apache.commons.jexl3;Expression;false;callable;;;Argument[-1];jexl",
"org.apache.commons.jexl3;JexlExpression;false;evaluate;;;Argument[-1];jexl",
"org.apache.commons.jexl3;JexlExpression;false;callable;;;Argument[-1];jexl",
"org.apache.commons.jexl3;Script;false;execute;;;Argument[-1];jexl",
"org.apache.commons.jexl3;Script;false;callable;;;Argument[-1];jexl",
"org.apache.commons.jexl3;JexlScript;false;execute;;;Argument[-1];jexl",
"org.apache.commons.jexl3;JexlScript;false;callable;;;Argument[-1];jexl",
"org.apache.commons.jexl3;JxltEngine$Expression;false;evaluate;;;Argument[-1];jexl",
"org.apache.commons.jexl3;JxltEngine$Expression;false;prepare;;;Argument[-1];jexl",
"org.apache.commons.jexl3;JxltEngine$Template;false;evaluate;;;Argument[-1];jexl"
]
}
}
/**
* A unit class for adding additional taint steps.
*
* Extend this class to add additional taint steps that should apply to the `JexlInjectionFlowConfig`.
*/
abstract class JexlInjectionAdditionalTaintStep extends Unit {
/**
* Holds if the step from `node1` to `node2` should be considered a taint
* step for the `JexlInjectionConfig` configuration.
*/
abstract predicate step(DataFlow::Node node1, DataFlow::Node node2);
}
/** A set of additional taint steps to consider when taint tracking JXEL related data flows. */
private class DefaultJexlInjectionAdditionalTaintStep extends JexlInjectionAdditionalTaintStep {
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
createJexlScriptStep(node1, node2) or
createJexlExpressionStep(node1, node2) or
createJexlTemplateStep(node1, node2)
}
}
/**
* Holds if `n1` to `n2` is a dataflow step that creates a JEXL script using an unsafe engine
* i.e. `tainted.createScript(jexlExpr)`.
*/
private predicate createJexlScriptStep(DataFlow::Node n1, DataFlow::Node n2) {
exists(MethodAccess ma, Method m | m = ma.getMethod() and n2.asExpr() = ma |
isUnsafeEngine(ma.getQualifier()) and
m instanceof CreateJexlScriptMethod and
n1.asExpr() = ma.getArgument(0) and
n1.asExpr().getType() instanceof TypeString
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that creates a JEXL expression using an unsafe engine
* i.e. `tainted.createExpression(jexlExpr)`.
*/
private predicate createJexlExpressionStep(DataFlow::Node n1, DataFlow::Node n2) {
exists(MethodAccess ma, Method m | m = ma.getMethod() and n2.asExpr() = ma |
isUnsafeEngine(ma.getQualifier()) and
m instanceof CreateJexlExpressionMethod and
n1.asExpr() = ma.getAnArgument() and
n1.asExpr().getType() instanceof TypeString
)
}
/**
* Holds if `n1` to `n2` is a dataflow step that creates a JEXL template using an unsafe engine
* i.e. `tainted.createTemplate(jexlExpr)`.
*/
private predicate createJexlTemplateStep(DataFlow::Node n1, DataFlow::Node n2) {
exists(MethodAccess ma, Method m, RefType taintType |
m = ma.getMethod() and n2.asExpr() = ma and taintType = n1.asExpr().getType()
|
isUnsafeEngine(ma.getQualifier()) and
m instanceof CreateJexlTemplateMethod and
n1.asExpr() = ma.getArgument([0, 1]) and
(taintType instanceof TypeString or taintType instanceof Reader)
)
}
/**
* Holds if `expr` is a JEXL engine that is not configured with a sandbox.
*/
private predicate isUnsafeEngine(Expr expr) {
not exists(SandboxedJexlFlowConfig config | config.hasFlowTo(DataFlow::exprNode(expr)))
}
/**
* A configuration for tracking sandboxed JEXL engines.
*/
private class SandboxedJexlFlowConfig extends DataFlow2::Configuration {
SandboxedJexlFlowConfig() { this = "JexlInjection::SandboxedJexlFlowConfig" }
override predicate isSource(DataFlow::Node node) { sourceNode(node, "sandboxed-jexl") }
override predicate isSink(DataFlow::Node node) { sinkNode(node, "sandboxed-jexl") }
override predicate isAdditionalFlowStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
createJexlEngineStep(fromNode, toNode)
}
}
private class SandoboxedJexlSourceModel extends SourceModelCsv {
override predicate row(string row) {
row =
[
// JEXL2
"org.apache.commons.jexl2;JexlEngine;false;JexlEngine;(Uberspect,JexlArithmetic,Map<String,Object>,Log);;ReturnValue;sandboxed-jexl",
// JEXL3
"org.apache.commons.jexl3;JexlBuilder;false;uberspect;(JexlUberspect);;ReturnValue;sandboxed-jexl",
"org.apache.commons.jexl3;JexlBuilder;false;sandbox;(JexlSandbox);;ReturnValue;sandboxed-jexl"
]
}
}
private class SandoboxedJexlSinkModel extends SinkModelCsv {
override predicate row(string row) {
row =
[
// JEXL2
"org.apache.commons.jexl2;JexlEngine;false;createScript;;;Argument[-1];sandboxed-jexl",
"org.apache.commons.jexl2;JexlEngine;false;createExpression;;;Argument[-1];sandboxed-jexl",
"org.apache.commons.jexl2;UnifiedJEXL;false;parse;;;Argument[-1];sandboxed-jexl",
"org.apache.commons.jexl2;UnifiedJEXL;false;createTemplate;;;Argument[-1];sandboxed-jexl",
// JEXL3
"org.apache.commons.jexl3;JexlEngine;false;createScript;;;Argument[-1];sandboxed-jexl",
"org.apache.commons.jexl3;JexlEngine;false;createExpression;;;Argument[-1];sandboxed-jexl",
"org.apache.commons.jexl3;JxltEngine;false;createExpression;;;Argument[-1];sandboxed-jexl",
"org.apache.commons.jexl3;JxltEngine;false;createTemplate;;;Argument[-1];sandboxed-jexl"
]
}
}
/**
* Holds if `fromNode` to `toNode` is a dataflow step that creates one of the JEXL engines.
*/
private predicate createJexlEngineStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
exists(MethodAccess ma, Method m | m = ma.getMethod() |
(m.getDeclaringType() instanceof JexlBuilder or m.getDeclaringType() instanceof JexlEngine) and
m.hasName(["create", "createJxltEngine"]) and
ma.getQualifier() = fromNode.asExpr() and
ma = toNode.asExpr()
)
or
exists(ConstructorCall cc |
cc.getConstructedType() instanceof UnifiedJexl and
cc.getArgument(0) = fromNode.asExpr() and
cc = toNode.asExpr()
)
}
/**
* A method that creates a JEXL script.
*/
private class CreateJexlScriptMethod extends Method {
CreateJexlScriptMethod() { getDeclaringType() instanceof JexlEngine and hasName("createScript") }
}
/**
* A method that creates a JEXL template.
*/
private class CreateJexlTemplateMethod extends Method {
CreateJexlTemplateMethod() {
(getDeclaringType() instanceof JxltEngine or getDeclaringType() instanceof UnifiedJexl) and
hasName("createTemplate")
}
}
/**
* A method that creates a JEXL expression.
*/
private class CreateJexlExpressionMethod extends Method {
CreateJexlExpressionMethod() {
(getDeclaringType() instanceof JexlEngine or getDeclaringType() instanceof JxltEngine) and
hasName("createExpression")
or
getDeclaringType() instanceof UnifiedJexl and hasName("parse")
}
}
private class JexlRefType extends RefType {
JexlRefType() { getPackage().hasName(["org.apache.commons.jexl2", "org.apache.commons.jexl3"]) }
}
private class JexlBuilder extends JexlRefType {
JexlBuilder() { hasName("JexlBuilder") }
}
private class JexlEngine extends JexlRefType {
JexlEngine() { hasName("JexlEngine") }
}
private class JxltEngine extends JexlRefType {
JxltEngine() { hasName("JxltEngine") }
}
private class UnifiedJexl extends JexlRefType {
UnifiedJexl() { hasName("UnifiedJEXL") }
}
private class Reader extends RefType {
Reader() { hasQualifiedName("java.io", "Reader") }
}