mirror of
https://github.com/github/codeql.git
synced 2025-12-22 03:36:30 +01:00
Moved from experimental
This commit is contained in:
63
java/ql/src/Security/CWE/CWE-094/JexlInjection.qhelp
Normal file
63
java/ql/src/Security/CWE/CWE-094/JexlInjection.qhelp
Normal 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>
|
||||
39
java/ql/src/Security/CWE/CWE-094/JexlInjection.ql
Normal file
39
java/ql/src/Security/CWE/CWE-094/JexlInjection.ql
Normal 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"
|
||||
241
java/ql/src/Security/CWE/CWE-094/JexlInjectionLib.qll
Normal file
241
java/ql/src/Security/CWE/CWE-094/JexlInjectionLib.qll
Normal 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") }
|
||||
}
|
||||
Reference in New Issue
Block a user