Merge pull request #3294 from ggolawski/ognl-injection

CodeQL query to detect OGNL injections
This commit is contained in:
Anders Schack-Mulligen
2020-06-30 09:46:02 +02:00
committed by GitHub
14 changed files with 397 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
import ognl.Ognl;
import ognl.OgnlException;
public void evaluate(HttpServletRequest request, Object root) throws OgnlException {
String expression = request.getParameter("expression");
// BAD: User provided expression is evaluated
Ognl.getValue(expression, root);
// GOOD: The name is validated and expression is evaluated in sandbox
System.setProperty("ognl.security.manager", ""); // Or add -Dognl.security.manager to JVM args
if (isValid(expression)) {
Ognl.getValue(expression, root);
} else {
// Reject the request
}
}

View File

@@ -0,0 +1,33 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Object-Graph Navigation Language (OGNL) is an open-source Expression Language (EL) for Java. Due
to its ability to create or change executable code, OGNL is capable of introducing critical
security flaws to any application that uses it. Evaluation of unvalidated expressions can let
attacker to modify Java objects' properties or execute arbitrary code.</p>
</overview>
<recommendation>
<p>The general recommendation is to not evaluate untrusted ONGL expressions. If user provided OGNL
expressions must be evaluated, do this in sandbox (add `-Dognl.security.manager` to JVM arguments)
and validate the expressions before evaluation.</p>
</recommendation>
<example>
<p>In the following examples, the code accepts an OGNL expression from the user and evaluates it.
</p>
<p>In the first example, the user provided OGNL expression is parsed and evaluated.</p>
<p>The second example validates the expression and evaluates it inside the sandbox.</p>
<sample src="OgnlInjection.java" />
</example>
<references>
<li><a href="https://github.com/jkuhnert/ognl/">OGNL library</a>.</li>
<li>Struts security: <a href="https://struts.apache.org/security/#proactively-protect-from-ognl-expression-injections-attacks-if-easily-applicable">Proactively protect from OGNL Expression Injections attacks</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,22 @@
/**
* @name OGNL Expression Language statement with user-controlled input
* @description Evaluation of OGNL Expression Language statement with user-controlled input can
* lead to execution of arbitrary code.
* @kind path-problem
* @problem.severity error
* @precision high
* @id java/ognl-injection
* @tags security
* external/cwe/cwe-917
*/
import java
import semmle.code.java.dataflow.FlowSources
import DataFlow
import DataFlow::PathGraph
import OgnlInjectionLib
from DataFlow::PathNode source, DataFlow::PathNode sink, OgnlInjectionFlowConfig conf
where conf.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "OGNL expression might include input from $@.",
source.getNode(), "this user input"

View File

@@ -0,0 +1,109 @@
import java
import semmle.code.java.dataflow.FlowSources
import DataFlow
import DataFlow::PathGraph
/**
* A taint-tracking configuration for unvalidated user input that is used in OGNL EL evaluation.
*/
class OgnlInjectionFlowConfig extends TaintTracking::Configuration {
OgnlInjectionFlowConfig() { this = "OgnlInjectionFlowConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof OgnlInjectionSink }
override predicate isSanitizer(DataFlow::Node node) {
node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType
}
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
parseCompileExpressionStep(node1, node2)
}
}
/** The class `org.apache.commons.ognl.Ognl` or `ognl.Ognl`. */
class TypeOgnl extends Class {
TypeOgnl() {
this.hasQualifiedName("org.apache.commons.ognl", "Ognl") or
this.hasQualifiedName("ognl", "Ognl")
}
}
/** The interface `org.apache.commons.ognl.Node` or `ognl.Node`. */
class TypeNode extends Interface {
TypeNode() {
this.hasQualifiedName("org.apache.commons.ognl", "Node") or
this.hasQualifiedName("ognl", "Node")
}
}
/** The class `com.opensymphony.xwork2.ognl.OgnlUtil`. */
class TypeOgnlUtil extends Class {
TypeOgnlUtil() { this.hasQualifiedName("com.opensymphony.xwork2.ognl", "OgnlUtil") }
}
/**
* OGNL sink for OGNL injection vulnerabilities, i.e. 1st argument to `getValue` or `setValue`
* method from `Ognl` or `getValue` or `setValue` method from `Node`.
*/
predicate ognlSinkMethod(Method m, int index) {
(
m.getDeclaringType() instanceof TypeOgnl
or
m.getDeclaringType().getAnAncestor*() instanceof TypeNode
) and
(
m.hasName("getValue") or
m.hasName("setValue")
) and
index = 0
}
/**
* Struts sink for OGNL injection vulnerabilities, i.e. 1st argument to `getValue`, `setValue` or
* `callMethod` method from `OgnlUtil`.
*/
predicate strutsSinkMethod(Method m, int index) {
m.getDeclaringType() instanceof TypeOgnlUtil and
(
m.hasName("getValue") or
m.hasName("setValue") or
m.hasName("callMethod")
) and
index = 0
}
/** Holds if parameter at index `index` in method `m` is OGNL injection sink. */
predicate ognlInjectionSinkMethod(Method m, int index) {
ognlSinkMethod(m, index) or
strutsSinkMethod(m, index)
}
/** A data flow sink for unvalidated user input that is used in OGNL EL evaluation. */
class OgnlInjectionSink extends DataFlow::ExprNode {
OgnlInjectionSink() {
exists(MethodAccess ma, Method m, int index |
ma.getMethod() = m and
(ma.getArgument(index) = this.getExpr() or ma.getQualifier() = this.getExpr()) and
ognlInjectionSinkMethod(m, index)
)
}
}
/**
* Holds if `n1` to `n2` is a dataflow step that converts between `String` and `Object` or `Node`,
* i.e. `Ognl.parseExpression(tainted)` or `Ognl.compileExpression(tainted)`.
*/
predicate parseCompileExpressionStep(ExprNode n1, ExprNode n2) {
exists(MethodAccess ma, Method m, int index |
n1.asExpr() = ma.getArgument(index) and
n2.asExpr() = ma and
ma.getMethod() = m and
m.getDeclaringType() instanceof TypeOgnl
|
m.hasName("parseExpression") and index = 0
or
m.hasName("compileExpression") and index = 2
)
}