Skip to content

Commit 921b8e8

Browse files
committed
Jshell Injection
1 parent 081fd28 commit 921b8e8

File tree

7 files changed

+152
-0
lines changed

7 files changed

+152
-0
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import javax.servlet.http.HttpServletRequest;
2+
import jdk.jshell.JShell;
3+
import jdk.jshell.SourceCodeAnalysis;
4+
import org.springframework.stereotype.Controller;
5+
import org.springframework.web.bind.annotation.GetMapping;
6+
7+
@Controller
8+
public class JShellInjection {
9+
10+
@GetMapping(value = "bad1")
11+
public void bad1(HttpServletRequest request) {
12+
String input = request.getParameter("code");
13+
JShell jShell = JShell.builder().build();
14+
// BAD: allow execution of arbitrary Java code
15+
jShell.eval(input);
16+
}
17+
18+
@GetMapping(value = "bad2")
19+
public void bad2(HttpServletRequest request) {
20+
String input = request.getParameter("code");
21+
JShell jShell = JShell.builder().build();
22+
SourceCodeAnalysis sourceCodeAnalysis = jShell.sourceCodeAnalysis();
23+
// BAD: allow execution of arbitrary Java code
24+
sourceCodeAnalysis.wrappers(input);
25+
}
26+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
<p>The Java Shell tool (JShell) is an interactive tool for learning the Java programming
8+
language and prototyping Java code. JShell is a Read-Evaluate-Print Loop (REPL), which
9+
evaluates declarations, statements, and expressions as they are entered and immediately
10+
shows the results. If an expression is built using attacker-controlled data and then evaluated,
11+
it may allow the attacker to run arbitrary code.</p>
12+
</overview>
13+
14+
<recommendation>
15+
<p>It is generally recommended to avoid using untrusted input in a JShell expression.
16+
If it is not possible,JShell expressions should be run in a sandbox that allows accessing only
17+
explicitly allowed classes.</p>
18+
</recommendation>
19+
20+
<example>
21+
<p>The following example calls <code>JShell.eval(...)</code> or <code>SourceCodeAnalysis.wrappers(...)</code>
22+
to execute untrusted data.</p>
23+
<sample src="JShellInjection.java" />
24+
</example>
25+
26+
<references>
27+
<li>
28+
Java 9 jshell tutorial: <a href="https://examples.javacodegeeks.com/core-java/java-9-jshell-tutorial/">JShell introduction</a>
29+
</li>
30+
</references>
31+
</qhelp>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* @name JShell injection
3+
* @description Evaluation of a user-controlled JShell expression
4+
* may lead to arbitrary code execution.
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @precision high
8+
* @id java/jshell-injection
9+
* @tags security
10+
* external/cwe-094
11+
*/
12+
13+
import java
14+
import JShellInjection
15+
import semmle.code.java.dataflow.FlowSources
16+
import DataFlow::PathGraph
17+
18+
class JShellInjectionConfiguration extends TaintTracking::Configuration {
19+
JShellInjectionConfiguration() { this = "JShellInjectionConfiguration" }
20+
21+
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
22+
23+
override predicate isSink(DataFlow::Node sink) { sink instanceof JShellInjectionSink }
24+
}
25+
26+
from DataFlow::PathNode source, DataFlow::PathNode sink, JShellInjectionConfiguration conf
27+
where conf.hasFlowPath(source, sink)
28+
select sink.getNode(), source, sink, "JShell injection from $@.", source.getNode(),
29+
"this user input"
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import java
2+
import semmle.code.java.dataflow.FlowSources
3+
4+
/** A sink for JShell expression injection vulnerabilities. */
5+
class JShellInjectionSink extends DataFlow::Node {
6+
JShellInjectionSink() {
7+
this.asExpr() = any(JShellEvalCall jsec).getArgument(0) or
8+
this.asExpr() = any(SourceCodeAnalysisWrappersCall scawc).getArgument(0)
9+
}
10+
}
11+
12+
/** A call to `JShell.eval`. */
13+
class JShellEvalCall extends MethodAccess {
14+
JShellEvalCall() {
15+
this.getMethod().hasName("eval") and
16+
this.getMethod().getDeclaringType().hasQualifiedName("jdk.jshell", "JShell") and
17+
this.getMethod().getNumberOfParameters() = 1
18+
}
19+
}
20+
21+
/** A call to `SourceCodeAnalysis.wrappers`. */
22+
class SourceCodeAnalysisWrappersCall extends MethodAccess {
23+
SourceCodeAnalysisWrappersCall() {
24+
this.getMethod().hasName("wrappers") and
25+
this.getMethod().getDeclaringType().hasQualifiedName("jdk.jshell", "SourceCodeAnalysis") and
26+
this.getMethod().getNumberOfParameters() = 1
27+
}
28+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
edges
2+
| JShellInjection.java:12:18:12:45 | getParameter(...) : String | JShellInjection.java:15:15:15:19 | input |
3+
| JShellInjection.java:20:18:20:45 | getParameter(...) : String | JShellInjection.java:24:31:24:35 | input |
4+
nodes
5+
| JShellInjection.java:12:18:12:45 | getParameter(...) : String | semmle.label | getParameter(...) : String |
6+
| JShellInjection.java:15:15:15:19 | input | semmle.label | input |
7+
| JShellInjection.java:20:18:20:45 | getParameter(...) : String | semmle.label | getParameter(...) : String |
8+
| JShellInjection.java:24:31:24:35 | input | semmle.label | input |
9+
#select
10+
| JShellInjection.java:15:15:15:19 | input | JShellInjection.java:12:18:12:45 | getParameter(...) : String | JShellInjection.java:15:15:15:19 | input | JShell injection from $@. | JShellInjection.java:12:18:12:45 | getParameter(...) | this user input |
11+
| JShellInjection.java:24:31:24:35 | input | JShellInjection.java:20:18:20:45 | getParameter(...) : String | JShellInjection.java:24:31:24:35 | input | JShell injection from $@. | JShellInjection.java:20:18:20:45 | getParameter(...) | this user input |
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import javax.servlet.http.HttpServletRequest;
2+
import jdk.jshell.JShell;
3+
import jdk.jshell.SourceCodeAnalysis;
4+
import org.springframework.stereotype.Controller;
5+
import org.springframework.web.bind.annotation.GetMapping;
6+
7+
@Controller
8+
public class JShellInjection {
9+
10+
@GetMapping(value = "bad1")
11+
public void bad1(HttpServletRequest request) {
12+
String input = request.getParameter("code");
13+
JShell jShell = JShell.builder().build();
14+
// BAD: allow execution of arbitrary Java code
15+
jShell.eval(input);
16+
}
17+
18+
@GetMapping(value = "bad2")
19+
public void bad2(HttpServletRequest request) {
20+
String input = request.getParameter("code");
21+
JShell jShell = JShell.builder().build();
22+
SourceCodeAnalysis sourceCodeAnalysis = jShell.sourceCodeAnalysis();
23+
// BAD: allow execution of arbitrary Java code
24+
sourceCodeAnalysis.wrappers(input);
25+
}
26+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
experimental/Security/CWE/CWE-094/JShellInjection.ql

0 commit comments

Comments
 (0)