Skip to content

Commit d664aa6

Browse files
committed
Include more scenarios and update qldoc
1 parent 852bcfb commit d664aa6

File tree

20 files changed

+633
-55
lines changed

20 files changed

+633
-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: 71 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,62 @@ 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+
this.hasName(["getProgram", "getMethodCallSyntax"])
2128
}
2229
}
2330

24-
/** The context class `org.mozilla.javascript.Context` of Rhino JavaScript Engine. */
31+
/** The context class `org.mozilla.javascript.Context` of Rhino Java Script Engine. */
2532
class RhinoContext extends RefType {
2633
RhinoContext() { this.hasQualifiedName("org.mozilla.javascript", "Context") }
2734
}
2835

29-
/**
30-
* A method that evaluates a Rhino expression.
31-
*/
36+
/** A method that evaluates a Rhino expression with `org.mozilla.javascript.Context`. */
3237
class RhinoEvaluateExpressionMethod extends Method {
3338
RhinoEvaluateExpressionMethod() {
3439
this.getDeclaringType().getAnAncestor*() instanceof RhinoContext and
35-
(
36-
hasName("evaluateString") or
37-
hasName("evaluateReader")
38-
)
40+
this.hasName([
41+
"evaluateString", "evaluateReader", "compileFunction", "compileReader", "compileString"
42+
])
3943
}
4044
}
4145

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

4980
/**
50-
* Holds if `ma` has Rhino code injection vulnerabilities.
81+
* Holds if a Rhino expression evaluation method has the code injection vulnerability.
5182
*/
5283
predicate evaluateRhinoExpression(MethodAccess ma, Expr sink) {
5384
exists(RhinoEvaluateExpressionMethod m | m = ma.getMethod() |
54-
sink = ma.getArgument(1) and // The second argument is the JavaScript or Java input
85+
(
86+
sink = ma.getArgument(1) and // The second argument is the JavaScript or Java input
87+
not ma.getMethod().getName() = "compileReader"
88+
or
89+
sink = ma.getArgument(0) and // The first argument is the input reader
90+
ma.getMethod().getName() = "compileReader"
91+
) and
5592
not exists(MethodAccess ca |
5693
(
5794
ca.getMethod().hasName("initSafeStandardObjects") // safe mode
@@ -63,15 +100,34 @@ predicate evaluateRhinoExpression(MethodAccess ma, Expr sink) {
63100
)
64101
}
65102

103+
/**
104+
* Holds if a Rhino expression compilation method has the code injection vulnerability.
105+
*/
106+
predicate compileScript(MethodAccess ma, Expr sink) {
107+
exists(RhinoCompileClassMethod m | m = ma.getMethod() | sink = ma.getArgument(0))
108+
}
109+
110+
/**
111+
* Holds if a Rhino class loading method has the code injection vulnerability.
112+
*/
113+
predicate defineClass(MethodAccess ma, Expr sink) {
114+
exists(RhinoDefineClassMethod m | m = ma.getMethod() | sink = ma.getArgument(1))
115+
}
116+
117+
/** A sink of script injection. */
66118
class ScriptInjectionSink extends DataFlow::ExprNode {
67119
ScriptInjectionSink() {
68120
scriptEngine(_, this.getExpr()) or
69-
evaluateRhinoExpression(_, this.getExpr())
121+
evaluateRhinoExpression(_, this.getExpr()) or
122+
compileScript(_, this.getExpr()) or
123+
defineClass(_, this.getExpr())
70124
}
71125

72126
MethodAccess getMethodAccess() {
73127
scriptEngine(result, this.getExpr()) or
74-
evaluateRhinoExpression(result, this.getExpr())
128+
evaluateRhinoExpression(result, this.getExpr()) or
129+
compileScript(result, this.getExpr()) or
130+
defineClass(result, this.getExpr())
75131
}
76132
}
77133

@@ -90,4 +146,4 @@ class ScriptInjectionConfiguration extends TaintTracking::Configuration {
90146
from DataFlow::PathNode source, DataFlow::PathNode sink, ScriptInjectionConfiguration conf
91147
where conf.hasFlowPath(source, sink)
92148
select sink.getNode().(ScriptInjectionSink).getMethodAccess(), source, sink,
93-
"JavaScript Engine evaluate $@.", source.getNode(), "user input"
149+
"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)