Skip to content

Commit af0f361

Browse files
Updated JexlInjection.ql to check for sandboxes
- Added a dataflow config to track setting a sandbox on JexlBuilder - Added SandboxedJexl3.java test
1 parent 59f48ec commit af0f361

File tree

6 files changed

+204
-5
lines changed

6 files changed

+204
-5
lines changed

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

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,10 @@ private class JexlEvaluationSink extends DataFlow::ExprNode {
4747
m instanceof CallableCallMethod and ma.getQualifier() = taintFrom
4848
or
4949
m instanceof JexlEngineGetSetPropertyMethod and
50-
ma.getAnArgument().getType() instanceof TypeString and
51-
ma.getAnArgument() = taintFrom
50+
exists(Expr arg, int index | arg = ma.getArgument(index) and index = [1, 2] |
51+
arg.getType() instanceof TypeString and
52+
arg = taintFrom
53+
)
5254
)
5355
}
5456
}
@@ -67,18 +69,21 @@ private class TaintPropagatingJexlMethodCall extends MethodAccess {
6769
|
6870
m instanceof CreateJexlScriptMethod and
6971
taintFromExpr = this.getArgument(0) and
70-
taintType instanceof TypeString
72+
taintType instanceof TypeString and
73+
isUnsafeEngine(this.getQualifier())
7174
or
7275
m instanceof CreateJexlCallableMethod and
7376
taintFromExpr = this.getQualifier()
7477
or
7578
m instanceof CreateJexlExpressionMethod and
7679
taintFromExpr = this.getAnArgument() and
77-
taintType instanceof TypeString
80+
taintType instanceof TypeString and
81+
isUnsafeEngine(this.getQualifier())
7882
or
7983
m instanceof CreateJexlTemplateMethod and
8084
(taintType instanceof TypeString or taintType instanceof Reader) and
81-
taintFromExpr = this.getArgument([0, 1])
85+
taintFromExpr = this.getArgument([0, 1]) and
86+
isUnsafeEngine(this.getQualifier())
8287
)
8388
}
8489

@@ -91,6 +96,51 @@ private class TaintPropagatingJexlMethodCall extends MethodAccess {
9196
}
9297
}
9398

99+
/**
100+
* Holds if `expr` is one of the Jexl engines that is not configured with a sandbox.
101+
*/
102+
private predicate isUnsafeEngine(Expr expr) {
103+
not exists(SandboxedJexlFlowConfig config | config.hasFlowTo(DataFlow::exprNode(expr)))
104+
}
105+
106+
/**
107+
* A configuration for a tracking sandboxed Jexl engines.
108+
*/
109+
private class SandboxedJexlFlowConfig extends DataFlow2::Configuration {
110+
SandboxedJexlFlowConfig() { this = "JexlInjection::SandboxedJexlFlowConfig" }
111+
112+
override predicate isSource(DataFlow::Node node) {
113+
exists(MethodAccess ma, Method m | m = ma.getMethod() |
114+
m.getDeclaringType() instanceof JexlBuilder and
115+
m.hasName(["uberspect", "sandbox"]) and
116+
m.getReturnType() instanceof JexlBuilder and
117+
(ma = node.asExpr() or ma.getQualifier() = node.asExpr())
118+
)
119+
}
120+
121+
override predicate isSink(DataFlow::Node node) {
122+
node.asExpr().getType() instanceof JexlEngine or
123+
node.asExpr().getType() instanceof JxltEngine or
124+
node.asExpr().getType() instanceof UnifiedJexl
125+
}
126+
127+
override predicate isAdditionalFlowStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
128+
createsJexlEngine(fromNode, toNode)
129+
}
130+
}
131+
132+
/**
133+
* Holds if `fromNode` to `toNode` is a dataflow step that creates one of the Jexl engines.
134+
*/
135+
private predicate createsJexlEngine(DataFlow::Node fromNode, DataFlow::Node toNode) {
136+
exists(MethodAccess ma, Method m | m = ma.getMethod() |
137+
(m.getDeclaringType() instanceof JexlBuilder or m.getDeclaringType() instanceof JexlEngine) and
138+
m.hasName(["create", "createJxltEngine"]) and
139+
ma.getQualifier() = fromNode.asExpr() and
140+
ma = toNode.asExpr()
141+
)
142+
}
143+
94144
/**
95145
* Holds if `fromNode` to `toNode` is a dataflow step that returns data from
96146
* a tainted bean by calling one of its getters.
@@ -190,6 +240,10 @@ private class JexlScript extends JexlRefType {
190240
JexlScript() { hasName(["Script", "JexlScript"]) }
191241
}
192242

243+
private class JexlBuilder extends JexlRefType {
244+
JexlBuilder() { hasName("JexlBuilder") }
245+
}
246+
193247
private class JexlEngine extends JexlRefType {
194248
JexlEngine() { hasName("JexlEngine") }
195249
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import java.net.ServerSocket;
2+
import java.net.Socket;
3+
import java.security.AccessControlException;
4+
import java.util.Arrays;
5+
import java.util.Iterator;
6+
import java.util.List;
7+
import java.util.function.Consumer;
8+
9+
import org.apache.commons.jexl3.*;
10+
import org.apache.commons.jexl3.introspection.*;
11+
12+
public class SandboxedJexl3 {
13+
14+
private static void runJexlExpressionWithSandbox(String jexlExpr) {
15+
JexlSandbox sandbox = new JexlSandbox(false);
16+
sandbox.white(SandboxedJexl3.class.getCanonicalName());
17+
JexlEngine jexl = new JexlBuilder().sandbox(sandbox).create();
18+
JexlExpression e = jexl.createExpression(jexlExpr);
19+
JexlContext jc = new MapContext();
20+
e.evaluate(jc);
21+
}
22+
23+
private static void runJexlExpressionWithUberspectSandbox(String jexlExpr) {
24+
JexlUberspect sandbox = new JexlUberspectSandbox();
25+
JexlEngine jexl = new JexlBuilder().uberspect(sandbox).create();
26+
JexlExpression e = jexl.createExpression(jexlExpr);
27+
JexlContext jc = new MapContext();
28+
e.evaluate(jc);
29+
}
30+
31+
private static JexlBuilder STATIC_JEXL_BUILDER;
32+
33+
static {
34+
JexlSandbox sandbox = new JexlSandbox(false);
35+
sandbox.white(SandboxedJexl3.class.getCanonicalName());
36+
STATIC_JEXL_BUILDER = new JexlBuilder().sandbox(sandbox);
37+
}
38+
39+
private static void runJexlExpressionViaJxltEngineWithSandbox(String jexlExpr) {
40+
JexlEngine jexl = STATIC_JEXL_BUILDER.create();
41+
JxltEngine jxlt = jexl.createJxltEngine();
42+
jxlt.createExpression(jexlExpr).evaluate(new MapContext());
43+
}
44+
45+
private static class JexlUberspectSandbox implements JexlUberspect {
46+
47+
}
48+
49+
private static void withSocket(Consumer<String> action) throws Exception {
50+
try (ServerSocket serverSocket = new ServerSocket(0)) {
51+
try (Socket socket = serverSocket.accept()) {
52+
byte[] bytes = new byte[1024];
53+
int n = socket.getInputStream().read(bytes);
54+
String jexlExpr = new String(bytes, 0, n);
55+
action.accept(jexlExpr);
56+
}
57+
}
58+
}
59+
60+
// below are examples of safer Jexl usage
61+
62+
// with JexlSandbox
63+
public static void saferJexlExpressionInSandbox() throws Exception {
64+
withSocket(SandboxedJexl3::runJexlExpressionWithSandbox);
65+
}
66+
67+
// with a custom sandbox implemented with JexlUberspect
68+
public static void saferJexlExpressionInUberspectSandbox() throws Exception {
69+
withSocket(SandboxedJexl3::runJexlExpressionWithUberspectSandbox);
70+
}
71+
72+
// with JexlSandbox and JxltEngine
73+
public static void saferJxltExpressionInSandbox() throws Exception {
74+
withSocket(SandboxedJexl3::runJexlExpressionViaJxltEngineWithSandbox);
75+
}
76+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
package org.apache.commons.jexl3;
22

3+
import org.apache.commons.jexl3.introspection.*;
4+
35
public class JexlBuilder {
46

57
public JexlEngine create() {
68
return null;
79
}
10+
11+
public JexlBuilder sandbox(JexlSandbox sandbox) {
12+
return null;
13+
}
14+
15+
public JexlBuilder uberspect(JexlUberspect uberspect) {
16+
return null;
17+
}
818
}

java/ql/test/stubs/apache-commons-jexl-3.1/org/apache/commons/jexl3/JexlEngine.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.apache.commons.jexl3;
22

3+
import org.apache.commons.jexl3.introspection.*;
4+
35
public abstract class JexlEngine {
46

57
public JexlExpression createExpression(JexlInfo info, String expression) {
@@ -31,4 +33,8 @@ public void setProperty(Object bean, String expr, Object value) {}
3133
public Object getProperty(Object bean, String expr) {
3234
return null;
3335
}
36+
37+
public JexlUberspect getUberspect() {
38+
return null;
39+
}
3440
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.apache.commons.jexl3.introspection;
2+
3+
public class JexlSandbox {
4+
5+
public JexlSandbox() {}
6+
7+
public JexlSandbox(boolean wb) {}
8+
9+
public JexlSandbox.Permissions white(String clazz) {
10+
return null;
11+
}
12+
13+
public static final class Permissions {
14+
15+
}
16+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package org.apache.commons.jexl3.introspection;
2+
3+
import java.util.Arrays;
4+
import java.util.Collections;
5+
import java.util.Iterator;
6+
import java.util.List;
7+
import java.util.Map;
8+
9+
public interface JexlUberspect {
10+
/*
11+
interface PropertyResolver {}
12+
13+
List<PropertyResolver> getResolvers(JexlOperator op, Object obj);
14+
15+
void setClassLoader(ClassLoader loader);
16+
17+
ClassLoader getClassLoader();
18+
19+
int getVersion();
20+
21+
JexlMethod getConstructor(Object ctorHandle, Object... args);
22+
23+
JexlMethod getMethod(Object obj, String method, Object... args);
24+
25+
JexlPropertyGet getPropertyGet(Object obj, Object identifier);
26+
27+
JexlPropertyGet getPropertyGet(List<PropertyResolver> resolvers, Object obj, Object identifier);
28+
29+
JexlPropertySet getPropertySet(Object obj, Object identifier, Object arg);
30+
31+
JexlPropertySet getPropertySet(List<PropertyResolver> resolvers, Object obj, Object identifier, Object arg);
32+
33+
Iterator<?> getIterator(Object obj);
34+
35+
JexlArithmetic.Uberspect getArithmetic(JexlArithmetic arithmetic);
36+
*/
37+
}

0 commit comments

Comments
 (0)