Merge pull request #5955 from haby0/java/JShellCodeInjection

Java: JShell Injection
This commit is contained in:
Chris Smowton
2021-06-24 17:03:30 +01:00
committed by GitHub
12 changed files with 404 additions and 1 deletions

View File

@@ -0,0 +1,40 @@
import javax.servlet.http.HttpServletRequest;
import jdk.jshell.JShell;
import jdk.jshell.SourceCodeAnalysis;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class JShellInjection {
@GetMapping(value = "bad1")
public void bad1(HttpServletRequest request) {
String input = request.getParameter("code");
JShell jShell = JShell.builder().build();
// BAD: allow execution of arbitrary Java code
jShell.eval(input);
}
@GetMapping(value = "bad2")
public void bad2(HttpServletRequest request) {
String input = request.getParameter("code");
JShell jShell = JShell.builder().build();
SourceCodeAnalysis sourceCodeAnalysis = jShell.sourceCodeAnalysis();
// BAD: allow execution of arbitrary Java code
sourceCodeAnalysis.wrappers(input);
}
@GetMapping(value = "bad3")
public void bad3(HttpServletRequest request) {
String input = request.getParameter("code");
JShell jShell = JShell.builder().build();
SourceCodeAnalysis.CompletionInfo info;
SourceCodeAnalysis sca = jShell.sourceCodeAnalysis();
for (info = sca.analyzeCompletion(input);
info.completeness().isComplete();
info = sca.analyzeCompletion(info.remaining())) {
// BAD: allow execution of arbitrary Java code
jShell.eval(info.source());
}
}
}

View File

@@ -0,0 +1,31 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>The Java Shell tool (JShell) is an interactive tool for learning the Java programming
language and prototyping Java code. JShell is a Read-Evaluate-Print Loop (REPL), which
evaluates declarations, statements, and expressions as they are entered and immediately
shows the results. If an expression is built using attacker-controlled data and then evaluated,
it may allow the attacker to run arbitrary code.</p>
</overview>
<recommendation>
<p>It is generally recommended to avoid using untrusted input in a JShell expression.
If it is not possible, JShell expressions should be run in a sandbox that allows accessing only
explicitly allowed classes.</p>
</recommendation>
<example>
<p>The following example calls <code>JShell.eval(...)</code> or <code>SourceCodeAnalysis.wrappers(...)</code>
to execute untrusted data.</p>
<sample src="JShellInjection.java" />
</example>
<references>
<li>
Java Shell Users Guide: <a href="https://docs.oracle.com/en/java/javase/11/jshell/introduction-jshell.html">Introduction to JShell</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,39 @@
/**
* @name JShell injection
* @description Evaluation of a user-controlled JShell expression
* may lead to arbitrary code execution.
* @kind path-problem
* @problem.severity error
* @precision high
* @id java/jshell-injection
* @tags security
* external/cwe-094
*/
import java
import JShellInjection
import semmle.code.java.dataflow.FlowSources
import DataFlow::PathGraph
class JShellInjectionConfiguration extends TaintTracking::Configuration {
JShellInjectionConfiguration() { this = "JShellInjectionConfiguration" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof JShellInjectionSink }
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(SourceCodeAnalysisAnalyzeCompletionCall scaacc |
scaacc.getArgument(0) = pred.asExpr() and scaacc = succ.asExpr()
)
or
exists(CompletionInfoSourceOrRemainingCall cisorc |
cisorc.getQualifier() = pred.asExpr() and cisorc = succ.asExpr()
)
}
}
from DataFlow::PathNode source, DataFlow::PathNode sink, JShellInjectionConfiguration conf
where conf.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "JShell injection from $@.", source.getNode(),
"this user input"

View File

@@ -0,0 +1,53 @@
import java
import semmle.code.java.dataflow.FlowSources
/** A sink for JShell expression injection vulnerabilities. */
class JShellInjectionSink extends DataFlow::Node {
JShellInjectionSink() {
this.asExpr() = any(JShellEvalCall jsec).getArgument(0)
or
this.asExpr() = any(SourceCodeAnalysisWrappersCall scawc).getArgument(0)
}
}
/** A call to `JShell.eval`. */
private class JShellEvalCall extends MethodAccess {
JShellEvalCall() {
this.getMethod().hasName("eval") and
this.getMethod().getDeclaringType().hasQualifiedName("jdk.jshell", "JShell") and
this.getMethod().getNumberOfParameters() = 1
}
}
/** A call to `SourceCodeAnalysis.wrappers`. */
private class SourceCodeAnalysisWrappersCall extends MethodAccess {
SourceCodeAnalysisWrappersCall() {
this.getMethod().hasName("wrappers") and
this.getMethod().getDeclaringType().hasQualifiedName("jdk.jshell", "SourceCodeAnalysis") and
this.getMethod().getNumberOfParameters() = 1
}
}
/** A call to `SourceCodeAnalysis.analyzeCompletion`. */
class SourceCodeAnalysisAnalyzeCompletionCall extends MethodAccess {
SourceCodeAnalysisAnalyzeCompletionCall() {
this.getMethod().hasName("analyzeCompletion") and
this.getMethod()
.getDeclaringType()
.getASupertype*()
.hasQualifiedName("jdk.jshell", "SourceCodeAnalysis") and
this.getMethod().getNumberOfParameters() = 1
}
}
/** A call to `CompletionInfo.source` or `CompletionInfo.remaining`. */
class CompletionInfoSourceOrRemainingCall extends MethodAccess {
CompletionInfoSourceOrRemainingCall() {
this.getMethod().getName() in ["source", "remaining"] and
this.getMethod()
.getDeclaringType()
.getASupertype*()
.hasQualifiedName("jdk.jshell", "SourceCodeAnalysis$CompletionInfo") and
this.getMethod().getNumberOfParameters() = 0
}
}