Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7d2d273

Browse files
Java: Added a source and a taint step for JexlInjectionConfig
- Added TaintedSpringRequestBody source - Added returningTaintedDataFromBean() taint step - Added tests
1 parent 99401f6 commit 7d2d273

9 files changed

Lines changed: 248 additions & 92 deletions

File tree

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class JexlInjectionConfig extends TaintTracking::Configuration {
1111
JexlInjectionConfig() { this = "JexlInjectionConfig" }
1212

1313
override predicate isSource(DataFlow::Node source) {
14+
source instanceof TaintedSpringRequestBody or
1415
source instanceof RemoteFlowSource or
1516
source instanceof UserInput or
1617
source instanceof EnvInput
@@ -22,7 +23,18 @@ class JexlInjectionConfig extends TaintTracking::Configuration {
2223
creatingTaintedJexlExpression(node1, node2) or
2324
creatingTaintedJexlTemplate(node1, node2) or
2425
creatingTaintedJexlScript(node1, node2) or
25-
creatingTaintedJexlCallable(node1, node2)
26+
creatingTaintedJexlCallable(node1, node2) or
27+
returningTaintedDataFromBean(node1, node2)
28+
}
29+
}
30+
31+
/**
32+
* A data flow source for parameters that have
33+
* a Spring framework annotation indicating remote user input from servlets.
34+
*/
35+
class TaintedSpringRequestBody extends DataFlow::Node {
36+
TaintedSpringRequestBody() {
37+
exists(SpringServletInputAnnotation a | this.asParameter().getAnAnnotation() = a)
2638
}
2739
}
2840

@@ -119,6 +131,18 @@ predicate creatingTaintedJexlCallable(DataFlow::Node node1, DataFlow::Node node2
119131
)
120132
}
121133

134+
/**
135+
* Holds if `node1` to `node2` is a dataflow step that returns data from
136+
* a tainted bean by calling one of its getters.
137+
*/
138+
predicate returningTaintedDataFromBean(DataFlow::Node node1, DataFlow::Node node2) {
139+
exists(MethodAccess ma, Method m | ma.getMethod() = m |
140+
m instanceof GetterMethod and
141+
ma.getQualifier() = node1.asExpr() and
142+
ma = node2.asExpr()
143+
)
144+
}
145+
122146
/**
123147
* Holds if `expr` is a call to one of the methods that execute a Jexl script.
124148
*/

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

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
import java.util.function.Consumer;
55

66
import org.apache.commons.jexl3.*;
7+
import org.springframework.http.HttpStatus;
8+
import org.springframework.http.ResponseEntity;
9+
import org.springframework.web.bind.annotation.PathVariable;
10+
import org.springframework.web.bind.annotation.PostMapping;
11+
import org.springframework.web.bind.annotation.RequestBody;
712

813
public class Jexl3Injection {
914

@@ -132,4 +137,58 @@ public static void testWithJxltEngineTemplateEvaluate() throws Exception {
132137
public static void testWithJexlExpressionCallable() throws Exception {
133138
testWithSocket(Jexl3Injection::runJexlExpressionViaCallable);
134139
}
140+
141+
@PostMapping("/request")
142+
public ResponseEntity testWithSpringControllerThatEvaluatesJexlFromPathVariable(
143+
@PathVariable String expr) {
144+
145+
runJexlExpression(expr);
146+
return ResponseEntity.ok(HttpStatus.OK);
147+
}
148+
149+
@PostMapping("/request")
150+
public ResponseEntity testWithSpringControllerThatEvaluatesJexlFromRequestBody(
151+
@RequestBody Data data) {
152+
153+
String expr = data.getExpr();
154+
runJexlExpression(expr);
155+
156+
return ResponseEntity.ok(HttpStatus.OK);
157+
}
158+
159+
@PostMapping("/request")
160+
public ResponseEntity testWithSpringControllerThatEvaluatesJexlFromRequestBodyWithNestedObjects(
161+
@RequestBody CustomRequest customRequest) {
162+
163+
String expr = customRequest.getData().getExpr();
164+
runJexlExpression(expr);
165+
166+
return ResponseEntity.ok(HttpStatus.OK);
167+
}
168+
169+
public static class CustomRequest {
170+
171+
private Data data;
172+
173+
CustomRequest(Data data) {
174+
this.data = data;
175+
}
176+
177+
public Data getData() {
178+
return data;
179+
}
180+
}
181+
182+
public static class Data {
183+
184+
private String expr;
185+
186+
Data(String expr) {
187+
this.expr = expr;
188+
}
189+
190+
public String getExpr() {
191+
return expr;
192+
}
193+
}
135194
}

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

Lines changed: 105 additions & 90 deletions
Large diffs are not rendered by default.

java/ql/test/stubs/springframework-5.2.3/org/springframework/http/ResponseEntity.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,8 @@ public class ResponseEntity<T> extends org.springframework.http.HttpEntity {
99

1010
// public ResponseEntity(T body, org.springframework.http.HttpStatus status) {
1111
// }
12+
13+
public static <T> ResponseEntity<T> ok(T body) {
14+
return null;
15+
}
1216
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package org.springframework.web.bind.annotation;
2+
3+
import java.lang.annotation.Documented;
4+
import java.lang.annotation.ElementType;
5+
import java.lang.annotation.Retention;
6+
import java.lang.annotation.RetentionPolicy;
7+
import java.lang.annotation.Target;
8+
9+
@Target(ElementType.PARAMETER)
10+
@Retention(RetentionPolicy.RUNTIME)
11+
@Documented
12+
public @interface PathVariable {
13+
14+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.springframework.web.bind.annotation;
2+
3+
import java.lang.annotation.Documented;
4+
import java.lang.annotation.ElementType;
5+
import java.lang.annotation.Retention;
6+
import java.lang.annotation.RetentionPolicy;
7+
import java.lang.annotation.Target;
8+
9+
@Target(ElementType.METHOD)
10+
@Retention(RetentionPolicy.RUNTIME)
11+
@Documented
12+
@RequestMapping(method = RequestMethod.POST)
13+
public @interface PostMapping {
14+
15+
String[] value() default {};
16+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.springframework.web.bind.annotation;
2+
3+
import java.lang.annotation.Documented;
4+
import java.lang.annotation.ElementType;
5+
import java.lang.annotation.Retention;
6+
import java.lang.annotation.RetentionPolicy;
7+
import java.lang.annotation.Target;
8+
9+
@Target(ElementType.PARAMETER)
10+
@Retention(RetentionPolicy.RUNTIME)
11+
@Documented
12+
public @interface RequestBody {
13+
14+
boolean required() default true;
15+
}

java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestMapping.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,7 @@
55
@Target(value={ElementType.METHOD,ElementType.TYPE})
66
@Retention(value=RetentionPolicy.RUNTIME)
77
@Documented
8-
public @interface RequestMapping { }
8+
public @interface RequestMapping {
9+
10+
RequestMethod[] method() default {};
11+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org.springframework.web.bind.annotation;
2+
3+
public enum RequestMethod {
4+
5+
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
6+
}

0 commit comments

Comments
 (0)