mirror of
https://github.com/github/codeql.git
synced 2025-12-24 04:36:35 +01:00
Merge pull request #5955 from haby0/java/JShellCodeInjection
Java: JShell Injection
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 User’s Guide: <a href="https://docs.oracle.com/en/java/javase/11/jshell/introduction-jshell.html">Introduction to JShell</a>
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -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"
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user