Merge pull request #5819 from luchua-bc/java/jpython-injection

Java: CWE-094 Jython code injection
This commit is contained in:
Chris Smowton
2021-05-18 16:38:40 +01:00
committed by GitHub
19 changed files with 1213 additions and 1 deletions

View File

@@ -0,0 +1,49 @@
import org.python.util.PythonInterpreter;
public class JythonInjection extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/plain");
String code = request.getParameter("code");
PythonInterpreter interpreter = null;
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
interpreter = new PythonInterpreter();
interpreter.setOut(out);
interpreter.setErr(out);
// BAD: allow execution of arbitrary Python code
interpreter.exec(code);
out.flush();
response.getWriter().print(out.toString());
} catch(PyException ex) {
response.getWriter().println(ex.getMessage());
} finally {
if (interpreter != null) {
interpreter.close();
}
out.close();
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/plain");
String code = request.getParameter("code");
PythonInterpreter interpreter = null;
try {
interpreter = new PythonInterpreter();
// BAD: allow execution of arbitrary Python code
PyObject py = interpreter.eval(code);
response.getWriter().print(py.toString());
} catch(PyException ex) {
response.getWriter().println(ex.getMessage());
} finally {
if (interpreter != null) {
interpreter.close();
}
}
}
}

View File

@@ -0,0 +1,34 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Python has been the most widely used programming language in recent years, and Jython
(formerly known as JPython) is a popular Java implementation of Python. It allows
embedded Python scripting inside Java applications and provides an interactive interpreter
that can be used to interact with Java packages or with running Java applications. 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>In general, including user input in Jython expression should be avoided. If user input
must be included in an expression, it should be then evaluated in a safe context that
doesn't allow arbitrary code invocation.</p>
</recommendation>
<example>
<p>The following code could execute arbitrary code in Jython Interpreter</p>
<sample src="JythonInjection.java" />
</example>
<references>
<li>
Jython Organization: <a href="https://jython.readthedocs.io/en/latest/JythonAndJavaIntegration/">Jython and Java Integration</a>
</li>
<li>
PortSwigger: <a href="https://portswigger.net/kb/issues/00100f10_python-code-injection">Python code injection</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,112 @@
/**
* @name Injection in Jython
* @description Evaluation of a user-controlled malicious expression in Java Python
* interpreter may lead to remote code execution.
* @kind path-problem
* @id java/jython-injection
* @tags security
* external/cwe/cwe-094
* external/cwe/cwe-095
*/
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.frameworks.spring.SpringController
import DataFlow::PathGraph
/** The class `org.python.util.PythonInterpreter`. */
class PythonInterpreter extends RefType {
PythonInterpreter() { this.hasQualifiedName("org.python.util", "PythonInterpreter") }
}
/** A method that evaluates, compiles or executes a Jython expression. */
class InterpretExprMethod extends Method {
InterpretExprMethod() {
this.getDeclaringType().getAnAncestor*() instanceof PythonInterpreter and
getName().matches(["exec%", "run%", "eval", "compile"])
}
}
/** The class `org.python.core.BytecodeLoader`. */
class BytecodeLoader extends RefType {
BytecodeLoader() { this.hasQualifiedName("org.python.core", "BytecodeLoader") }
}
/** Holds if a Jython expression if evaluated, compiled or executed. */
predicate runsCode(MethodAccess ma, Expr sink) {
exists(Method m | m = ma.getMethod() |
m instanceof InterpretExprMethod and
sink = ma.getArgument(0)
)
}
/** A method that loads Java class data. */
class LoadClassMethod extends Method {
LoadClassMethod() {
this.getDeclaringType().getAnAncestor*() instanceof BytecodeLoader and
hasName(["makeClass", "makeCode"])
}
}
/**
* Holds if `ma` is a call to a class-loading method, and `sink` is the byte array
* representing the class to be loaded.
*/
predicate loadsClass(MethodAccess ma, Expr sink) {
exists(Method m, int i | m = ma.getMethod() |
m instanceof LoadClassMethod and
m.getParameter(i).getType() instanceof Array and // makeClass(java.lang.String name, byte[] data, ...)
sink = ma.getArgument(i)
)
}
/** The class `org.python.core.Py`. */
class Py extends RefType {
Py() { this.hasQualifiedName("org.python.core", "Py") }
}
/** A method declared on class `Py` or one of its descendants that compiles Python code. */
class PyCompileMethod extends Method {
PyCompileMethod() {
this.getDeclaringType().getAnAncestor*() instanceof Py and
getName().matches("compile%")
}
}
/** Holds if source code is compiled with `PyCompileMethod`. */
predicate compile(MethodAccess ma, Expr sink) {
exists(Method m | m = ma.getMethod() |
m instanceof PyCompileMethod and
sink = ma.getArgument(0)
)
}
/** An expression loaded by Jython. */
class CodeInjectionSink extends DataFlow::ExprNode {
MethodAccess methodAccess;
CodeInjectionSink() {
runsCode(methodAccess, this.getExpr()) or
loadsClass(methodAccess, this.getExpr()) or
compile(methodAccess, this.getExpr())
}
MethodAccess getMethodAccess() { result = methodAccess }
}
/**
* A taint configuration for tracking flow from `RemoteFlowSource` to a Jython method call
* `CodeInjectionSink` that executes injected code.
*/
class CodeInjectionConfiguration extends TaintTracking::Configuration {
CodeInjectionConfiguration() { this = "CodeInjectionConfiguration" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof CodeInjectionSink }
}
from DataFlow::PathNode source, DataFlow::PathNode sink, CodeInjectionConfiguration conf
where conf.hasFlowPath(source, sink)
select sink.getNode().(CodeInjectionSink).getMethodAccess(), source, sink, "Jython evaluate $@.",
source.getNode(), "user input"