Skip to content

Commit aa2925d

Browse files
committed
Include more scenarios and update qldoc
1 parent 207f920 commit aa2925d

File tree

20 files changed

+636
-55
lines changed

20 files changed

+636
-55
lines changed

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<qhelp>
55

66
<overview>
7-
<p>The JavaScript Engine API has been available since the release of Java 6, which allows
7+
<p>The Java Scripting API has been available since the release of Java 6, which allows
88
applications to interact with scripts written in languages such as JavaScript. It serves
99
as an embedded scripting engine inside Java applications which allows Java-to-JavaScript
1010
interoperability and provides a seamless integration between the two languages. If an
@@ -13,11 +13,11 @@
1313
</overview>
1414

1515
<recommendation>
16-
<p>In general, including user input in a JavaScript Engine expression should be avoided.
16+
<p>In general, including user input in a Java Script Engine expression should be avoided.
1717
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>
18+
context that doesn't allow arbitrary code invocation. Use "Cloudbees Rhino Sandbox" or
19+
sandboxing with SecurityManager, which will be deprecated in a future release, or use
20+
<a href="https://www.graalvm.org/">GraalVM</a> instead.</p>
2121
</recommendation>
2222

2323
<example>
@@ -36,14 +36,17 @@
3636
<li>
3737
CERT coding standard: <a href="https://wiki.sei.cmu.edu/confluence/display/java/IDS52-J.+Prevent+code+injection">ScriptEngine code injection</a>
3838
</li>
39+
<li>
40+
GraalVM: <a href="https://www.graalvm.org/reference-manual/js/NashornMigrationGuide/#secure-by-default">Secure by Default</a>
41+
</li>
3942
<li>
4043
Mozilla Rhino: <a href="https://github.com/mozilla/rhino">Rhino: JavaScript in Java</a>
4144
</li>
4245
<li>
4346
Rhino Sandbox: <a href="https://github.com/javadelight/delight-rhino-sandbox">A sandbox to execute JavaScript code with Rhino in Java</a>
4447
</li>
4548
<li>
46-
GuardRails: <a href="https://docs.guardrails.io/docs/en/vulnerabilities/java/insecure_use_of_dangerous_function">Code Injection</a>
49+
GuardRails: <a href="https://docs.guardrails.io/docs/en/vulnerabilities/java/insecure_use_of_dangerous_function#code-injection">Code Injection</a>
4750
</li>
4851
</references>
4952
</qhelp>

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

Lines changed: 74 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
2-
* @name Injection in JavaScript Engine
2+
* @name Injection in Java Script Engine
33
* @description Evaluation of a user-controlled malicious JavaScript or Java expression in
4-
* JavaScript Engine may lead to remote code execution.
4+
* Java Script Engine may lead to remote code execution.
55
* @kind path-problem
66
* @problem.severity error
77
* @precision high
@@ -14,31 +14,65 @@ import java
1414
import semmle.code.java.dataflow.FlowSources
1515
import DataFlow::PathGraph
1616

17+
/** A method of ScriptEngine that allows code injection. */
1718
class ScriptEngineMethod extends Method {
1819
ScriptEngineMethod() {
1920
this.getDeclaringType().getASupertype*().hasQualifiedName("javax.script", "ScriptEngine") and
2021
this.hasName("eval")
22+
or
23+
this.getDeclaringType().getASupertype*().hasQualifiedName("javax.script", "Compilable") and
24+
this.hasName("compile")
25+
or
26+
this.getDeclaringType().getASupertype*().hasQualifiedName("javax.script", "ScriptEngineFactory") and
27+
(
28+
this.hasName("getProgram") or
29+
this.hasName("getMethodCallSyntax")
30+
)
2131
}
2232
}
2333

24-
/** The context class `org.mozilla.javascript.Context` of Rhino JavaScript Engine. */
34+
/** The context class `org.mozilla.javascript.Context` of Rhino Java Script Engine. */
2535
class RhinoContext extends RefType {
2636
RhinoContext() { this.hasQualifiedName("org.mozilla.javascript", "Context") }
2737
}
2838

29-
/**
30-
* A method that evaluates a Rhino expression.
31-
*/
39+
/** A method that evaluates a Rhino expression with `org.mozilla.javascript.Context`. */
3240
class RhinoEvaluateExpressionMethod extends Method {
3341
RhinoEvaluateExpressionMethod() {
3442
this.getDeclaringType().getAnAncestor*() instanceof RhinoContext and
35-
(
36-
hasName("evaluateString") or
37-
hasName("evaluateReader")
38-
)
43+
this.hasName([
44+
"evaluateString", "evaluateReader", "compileFunction", "compileReader", "compileString"
45+
])
3946
}
4047
}
4148

49+
/**
50+
* A method that compiles a Rhino expression with
51+
* `org.mozilla.javascript.optimizer.ClassCompiler`.
52+
*/
53+
class RhinoCompileClassMethod extends Method {
54+
RhinoCompileClassMethod() {
55+
this.getDeclaringType()
56+
.getASupertype*()
57+
.hasQualifiedName("org.mozilla.javascript.optimizer", "ClassCompiler") and
58+
this.hasName("compileToClassFiles")
59+
}
60+
}
61+
62+
/**
63+
* A method that defines a Java class from a Rhino expression with
64+
* `org.mozilla.javascript.GeneratedClassLoader`.
65+
*/
66+
class RhinoDefineClassMethod extends Method {
67+
RhinoDefineClassMethod() {
68+
this.getDeclaringType()
69+
.getASupertype*()
70+
.hasQualifiedName("org.mozilla.javascript", "GeneratedClassLoader") and
71+
this.hasName("defineClass")
72+
}
73+
}
74+
75+
/** Holds if `ma` is a method access of `ScriptEngineMethod`. */
4276
predicate scriptEngine(MethodAccess ma, Expr sink) {
4377
exists(Method m | m = ma.getMethod() |
4478
m instanceof ScriptEngineMethod and
@@ -47,11 +81,17 @@ predicate scriptEngine(MethodAccess ma, Expr sink) {
4781
}
4882

4983
/**
50-
* Holds if `ma` has Rhino code injection vulnerabilities.
84+
* Holds if a Rhino expression evaluation method has the code injection vulnerability.
5185
*/
5286
predicate evaluateRhinoExpression(MethodAccess ma, Expr sink) {
5387
exists(RhinoEvaluateExpressionMethod m | m = ma.getMethod() |
54-
sink = ma.getArgument(1) and // The second argument is the JavaScript or Java input
88+
(
89+
sink = ma.getArgument(1) and // The second argument is the JavaScript or Java input
90+
not ma.getMethod().getName() = "compileReader"
91+
or
92+
sink = ma.getArgument(0) and // The first argument is the input reader
93+
ma.getMethod().getName() = "compileReader"
94+
) and
5595
not exists(MethodAccess ca |
5696
(
5797
ca.getMethod().hasName("initSafeStandardObjects") // safe mode
@@ -63,15 +103,34 @@ predicate evaluateRhinoExpression(MethodAccess ma, Expr sink) {
63103
)
64104
}
65105

106+
/**
107+
* Holds if a Rhino expression compilation method has the code injection vulnerability.
108+
*/
109+
predicate compileScript(MethodAccess ma, Expr sink) {
110+
exists(RhinoCompileClassMethod m | m = ma.getMethod() | sink = ma.getArgument(0))
111+
}
112+
113+
/**
114+
* Holds if a Rhino class loading method has the code injection vulnerability.
115+
*/
116+
predicate defineClass(MethodAccess ma, Expr sink) {
117+
exists(RhinoDefineClassMethod m | m = ma.getMethod() | sink = ma.getArgument(1))
118+
}
119+
120+
/** A sink of script injection. */
66121
class ScriptInjectionSink extends DataFlow::ExprNode {
67122
ScriptInjectionSink() {
68123
scriptEngine(_, this.getExpr()) or
69-
evaluateRhinoExpression(_, this.getExpr())
124+
evaluateRhinoExpression(_, this.getExpr()) or
125+
compileScript(_, this.getExpr()) or
126+
defineClass(_, this.getExpr())
70127
}
71128

72129
MethodAccess getMethodAccess() {
73130
scriptEngine(result, this.getExpr()) or
74-
evaluateRhinoExpression(result, this.getExpr())
131+
evaluateRhinoExpression(result, this.getExpr()) or
132+
compileScript(result, this.getExpr()) or
133+
defineClass(result, this.getExpr())
75134
}
76135
}
77136

@@ -90,4 +149,4 @@ class ScriptInjectionConfiguration extends TaintTracking::Configuration {
90149
from DataFlow::PathNode source, DataFlow::PathNode sink, ScriptInjectionConfiguration conf
91150
where conf.hasFlowPath(source, sink)
92151
select sink.getNode().(ScriptInjectionSink).getMethodAccess(), source, sink,
93-
"JavaScript Engine evaluate $@.", source.getNode(), "user input"
152+
"Java Script Engine evaluate $@.", source.getNode(), "user input"

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55
import javax.servlet.http.HttpServletResponse;
66

77
import org.mozilla.javascript.ClassShutter;
8+
import org.mozilla.javascript.CompilerEnvirons;
89
import org.mozilla.javascript.Context;
10+
import org.mozilla.javascript.DefiningClassLoader;
911
import org.mozilla.javascript.Scriptable;
1012
import org.mozilla.javascript.RhinoException;
13+
import org.mozilla.javascript.optimizer.ClassCompiler;
1114

1215
/**
1316
* Servlet implementation class RhinoServlet
@@ -75,4 +78,17 @@ public boolean visibleToScripts(String className) {
7578
Context.exit();
7679
}
7780
}
81+
82+
// BAD: allow arbitrary code to be compiled for subsequent execution
83+
protected void doGet2(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
84+
String code = request.getParameter("code");
85+
ClassCompiler compiler = new ClassCompiler(new CompilerEnvirons());
86+
Object[] objs = compiler.compileToClassFiles(code, "/sourceLocation", 1, "mainClassName");
87+
}
88+
89+
// BAD: allow arbitrary code to be loaded for subsequent execution
90+
protected void doPost2(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
91+
String code = request.getParameter("code");
92+
Class clazz = new DefiningClassLoader().defineClass("Powerfunc", code.getBytes());
93+
}
7894
}

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

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,26 +33,53 @@ public void testCustomScriptEngineReference(String input) throws ScriptException
3333
MyCustomScriptEngine engine = (MyCustomScriptEngine) factory.getScriptEngine(new String[] { "-scripting" });
3434
Object result = engine.eval(input);
3535
}
36+
37+
public void testScriptEngineCompilable(String input) throws ScriptException {
38+
NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
39+
Compilable engine = (Compilable) factory.getScriptEngine(new String[] { "-scripting" });
40+
CompiledScript script = engine.compile(input);
41+
Object result = script.eval();
42+
}
43+
44+
public void testScriptEngineGetProgram(String input) throws ScriptException {
45+
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
46+
ScriptEngine engine = scriptEngineManager.getEngineByName("nashorn");
47+
String program = engine.getFactory().getProgram(input);
48+
Object result = engine.eval(program);
49+
}
3650

3751
public static void main(String[] args) throws ScriptException {
3852
new ScriptEngineTest().testWithScriptEngineReference(args[0]);
3953
new ScriptEngineTest().testNashornWithScriptEngineReference(args[0]);
4054
new ScriptEngineTest().testNashornWithNashornScriptEngineReference(args[0]);
4155
new ScriptEngineTest().testCustomScriptEngineReference(args[0]);
56+
new ScriptEngineTest().testScriptEngineCompilable(args[0]);
57+
new ScriptEngineTest().testScriptEngineGetProgram(args[0]);
4258
}
4359

4460
private static class MyCustomScriptEngine extends AbstractScriptEngine {
45-
public Object eval(String var1) throws ScriptException {
46-
return null;
47-
}
61+
public Object eval(String var1) throws ScriptException { return null; }
62+
63+
@Override
64+
public ScriptEngineFactory getFactory() { return null; }
4865
}
4966

5067
private static class MyCustomFactory implements ScriptEngineFactory {
5168
public MyCustomFactory() {
52-
}
53-
54-
public ScriptEngine getScriptEngine() { return null; }
69+
}
70+
71+
@Override
72+
public ScriptEngine getScriptEngine() { return null; }
5573

5674
public ScriptEngine getScriptEngine(String... args) { return null; }
75+
76+
@Override
77+
public String getEngineName() { return null; }
78+
79+
@Override
80+
public String getMethodCallSyntax(final String obj, final String method, final String... args) { return null; }
81+
82+
@Override
83+
public String getProgram(final String... statements) { return null; }
5784
}
5885
}

0 commit comments

Comments
 (0)