Skip to content

Commit 852bcfb

Browse files
committed
Refactor the ScriptEngine query and the Rhino code injection query into one
1 parent b0b5338 commit 852bcfb

File tree

12 files changed

+151
-214
lines changed

12 files changed

+151
-214
lines changed

java/ql/src/experimental/Security/CWE/CWE-094/RhinoInjection.qhelp

Lines changed: 0 additions & 51 deletions
This file was deleted.

java/ql/src/experimental/Security/CWE/CWE-094/RhinoInjection.ql

Lines changed: 0 additions & 17 deletions
This file was deleted.

java/ql/src/experimental/Security/CWE/CWE-094/RhinoInjection.qll

Lines changed: 0 additions & 56 deletions
This file was deleted.

java/ql/src/experimental/Security/CWE/CWE-094/ScriptEngine.qhelp

Lines changed: 0 additions & 26 deletions
This file was deleted.

java/ql/src/experimental/Security/CWE/CWE-094/ScriptEngine.ql

Lines changed: 0 additions & 51 deletions
This file was deleted.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
<p>The JavaScript Engine API has been available since the release of Java 6, which allows
8+
applications to interact with scripts written in languages such as JavaScript. It serves
9+
as an embedded scripting engine inside Java applications which allows Java-to-JavaScript
10+
interoperability and provides a seamless integration between the two languages. If an
11+
expression is built using attacker-controlled data, and then evaluated in a powerful
12+
context, it may allow the attacker to run arbitrary code.</p>
13+
</overview>
14+
15+
<recommendation>
16+
<p>In general, including user input in a JavaScript Engine expression should be avoided.
17+
If user input must be included in the expression, it should be then evaluated in a safe
18+
context that doesn't allow arbitrary code invocation. Use "Cloudbees Rhino Sandbox" or
19+
sandboxing with SecurityManager or use <a href="https://www.graalvm.org/">graalvm</a>
20+
instead.</p>
21+
</recommendation>
22+
23+
<example>
24+
<p>The following code could execute random JavaScript code in <code>ScriptEngine</code></p>
25+
<sample src="ScriptEngine.java" />
26+
<sample src="NashornScriptEngine.java" />
27+
28+
<p>The following example shows two ways of using Rhino expression. In the 'BAD' case,
29+
an unsafe context is initialized with <code>initStandardObjects</code> that allows arbitrary
30+
Java code to be executed. In the 'GOOD' case, a safe context is initialized with
31+
<code>initSafeStandardObjects</code> or <code>setClassShutter</code>.</p>
32+
<sample src="RhinoInjection.java" />
33+
</example>
34+
35+
<references>
36+
<li>
37+
CERT coding standard: <a href="https://wiki.sei.cmu.edu/confluence/display/java/IDS52-J.+Prevent+code+injection">ScriptEngine code injection</a>
38+
</li>
39+
<li>
40+
Mozilla Rhino: <a href="https://github.com/mozilla/rhino">Rhino: JavaScript in Java</a>
41+
</li>
42+
<li>
43+
Rhino Sandbox: <a href="https://github.com/javadelight/delight-rhino-sandbox">A sandbox to execute JavaScript code with Rhino in Java</a>
44+
</li>
45+
<li>
46+
GuardRails: <a href="https://docs.guardrails.io/docs/en/vulnerabilities/java/insecure_use_of_dangerous_function">Code Injection</a>
47+
</li>
48+
</references>
49+
</qhelp>
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* @name Injection in JavaScript Engine
3+
* @description Evaluation of a user-controlled malicious JavaScript or Java expression in
4+
* JavaScript Engine may lead to remote code execution.
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @precision high
8+
* @id java/unsafe-eval
9+
* @tags security
10+
* external/cwe/cwe-094
11+
*/
12+
13+
import java
14+
import semmle.code.java.dataflow.FlowSources
15+
import DataFlow::PathGraph
16+
17+
class ScriptEngineMethod extends Method {
18+
ScriptEngineMethod() {
19+
this.getDeclaringType().getASupertype*().hasQualifiedName("javax.script", "ScriptEngine") and
20+
this.hasName("eval")
21+
}
22+
}
23+
24+
/** The context class `org.mozilla.javascript.Context` of Rhino JavaScript Engine. */
25+
class RhinoContext extends RefType {
26+
RhinoContext() { this.hasQualifiedName("org.mozilla.javascript", "Context") }
27+
}
28+
29+
/**
30+
* A method that evaluates a Rhino expression.
31+
*/
32+
class RhinoEvaluateExpressionMethod extends Method {
33+
RhinoEvaluateExpressionMethod() {
34+
this.getDeclaringType().getAnAncestor*() instanceof RhinoContext and
35+
(
36+
hasName("evaluateString") or
37+
hasName("evaluateReader")
38+
)
39+
}
40+
}
41+
42+
predicate scriptEngine(MethodAccess ma, Expr sink) {
43+
exists(Method m | m = ma.getMethod() |
44+
m instanceof ScriptEngineMethod and
45+
sink = ma.getArgument(0)
46+
)
47+
}
48+
49+
/**
50+
* Holds if `ma` has Rhino code injection vulnerabilities.
51+
*/
52+
predicate evaluateRhinoExpression(MethodAccess ma, Expr sink) {
53+
exists(RhinoEvaluateExpressionMethod m | m = ma.getMethod() |
54+
sink = ma.getArgument(1) and // The second argument is the JavaScript or Java input
55+
not exists(MethodAccess ca |
56+
(
57+
ca.getMethod().hasName("initSafeStandardObjects") // safe mode
58+
or
59+
ca.getMethod().hasName("setClassShutter") // `ClassShutter` constraint is enforced
60+
) and
61+
ma.getQualifier() = ca.getQualifier().(VarAccess).getVariable().getAnAccess()
62+
)
63+
)
64+
}
65+
66+
class ScriptInjectionSink extends DataFlow::ExprNode {
67+
ScriptInjectionSink() {
68+
scriptEngine(_, this.getExpr()) or
69+
evaluateRhinoExpression(_, this.getExpr())
70+
}
71+
72+
MethodAccess getMethodAccess() {
73+
scriptEngine(result, this.getExpr()) or
74+
evaluateRhinoExpression(result, this.getExpr())
75+
}
76+
}
77+
78+
class ScriptInjectionConfiguration extends TaintTracking::Configuration {
79+
ScriptInjectionConfiguration() { this = "ScriptInjectionConfiguration" }
80+
81+
override predicate isSource(DataFlow::Node source) {
82+
source instanceof RemoteFlowSource
83+
or
84+
source instanceof LocalUserInput
85+
}
86+
87+
override predicate isSink(DataFlow::Node sink) { sink instanceof ScriptInjectionSink }
88+
}
89+
90+
from DataFlow::PathNode source, DataFlow::PathNode sink, ScriptInjectionConfiguration conf
91+
where conf.hasFlowPath(source, sink)
92+
select sink.getNode().(ScriptInjectionSink).getMethodAccess(), source, sink,
93+
"JavaScript Engine evaluate $@.", source.getNode(), "user input"

java/ql/test/experimental/query-tests/security/CWE-094/RhinoInjection.expected

Lines changed: 0 additions & 7 deletions
This file was deleted.

java/ql/test/experimental/query-tests/security/CWE-094/RhinoInjection.qlref

Lines changed: 0 additions & 1 deletion
This file was deleted.

java/ql/test/experimental/query-tests/security/CWE-094/ScriptEngine.qlref

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)