mirror of
https://github.com/github/codeql.git
synced 2026-01-06 11:10:23 +01:00
Merge pull request #3294 from ggolawski/ognl-injection
CodeQL query to detect OGNL injections
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user