Skip to content

Commit 703fbf1

Browse files
committed
Add more methods and update the library name
1 parent 4709e81 commit 703fbf1

25 files changed

+699
-227
lines changed

java/ql/src/experimental/Security/CWE/CWE-094/JPythonInjection.java

Lines changed: 0 additions & 48 deletions
This file was deleted.

java/ql/src/experimental/Security/CWE/CWE-094/JPythonInjection.qhelp

Lines changed: 0 additions & 34 deletions
This file was deleted.

java/ql/src/experimental/Security/CWE/CWE-094/JPythonInjection.ql

Lines changed: 0 additions & 68 deletions
This file was deleted.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
public class JythonInjection extends HttpServlet {
2+
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
3+
response.setContentType("text/plain");
4+
String code = request.getParameter("code");
5+
PythonInterpreter interpreter = null;
6+
ByteArrayOutputStream out = new ByteArrayOutputStream();
7+
8+
try {
9+
interpreter = new PythonInterpreter();
10+
interpreter.setOut(out);
11+
interpreter.setErr(out);
12+
13+
// BAD: allow arbitrary Jython expression to execute
14+
interpreter.exec(code);
15+
out.flush();
16+
17+
response.getWriter().print(out.toString());
18+
} catch(PyException ex) {
19+
response.getWriter().println(ex.getMessage());
20+
} finally {
21+
if (interpreter != null) {
22+
interpreter.close();
23+
}
24+
out.close();
25+
}
26+
}
27+
28+
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
29+
response.setContentType("text/plain");
30+
String code = request.getParameter("code");
31+
PythonInterpreter interpreter = null;
32+
33+
try {
34+
interpreter = new PythonInterpreter();
35+
// BAD: allow arbitrary Jython expression to evaluate
36+
PyObject py = interpreter.eval(code);
37+
38+
response.getWriter().print(py.toString());
39+
} catch(PyException ex) {
40+
response.getWriter().println(ex.getMessage());
41+
} finally {
42+
if (interpreter != null) {
43+
interpreter.close();
44+
}
45+
}
46+
}
47+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
<p>Python has been the most widely used programming language in recent years, and Jython
8+
(formerly known as JPython) is a popular Java implementation of Python. It allows
9+
embedded Python scripting inside Java applications and provides an interactive interpreter
10+
that can be used to interact with Java packages or with running Java applications. If an
11+
expression is built using attacker-controlled data and then evaluated, it may allow the
12+
attacker to run arbitrary code.</p>
13+
</overview>
14+
15+
<recommendation>
16+
<p>In general, including user input in Jython expression should be avoided. If user input
17+
must be included in an expression, it should be then evaluated in a safe context that
18+
doesn't allow arbitrary code invocation.</p>
19+
</recommendation>
20+
21+
<example>
22+
<p>The following code could execute arbitrary code in Jython Interpreter</p>
23+
<sample src="JythonInjection.java" />
24+
</example>
25+
26+
<references>
27+
<li>
28+
Jython Organization: <a href="https://jython.readthedocs.io/en/latest/JythonAndJavaIntegration/">Jython and Java Integration</a>
29+
</li>
30+
<li>
31+
PortSwigger: <a href="https://portswigger.net/kb/issues/00100f10_python-code-injection">Python code injection</a>
32+
</li>
33+
</references>
34+
</qhelp>
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/**
2+
* @name Injection in Jython
3+
* @description Evaluation of a user-controlled malicious expression in Java Python
4+
* interpreter may lead to remote code execution.
5+
* @kind path-problem
6+
* @id java/jython-injection
7+
* @tags security
8+
* external/cwe/cwe-094
9+
* external/cwe/cwe-095
10+
*/
11+
12+
import java
13+
import semmle.code.java.dataflow.FlowSources
14+
import DataFlow::PathGraph
15+
16+
/** The class `org.python.util.PythonInterpreter`. */
17+
class PythonInterpreter extends RefType {
18+
PythonInterpreter() { this.hasQualifiedName("org.python.util", "PythonInterpreter") }
19+
}
20+
21+
/** A method that evaluates, compiles or executes a Jython expression. */
22+
class InterpretExprMethod extends Method {
23+
InterpretExprMethod() {
24+
this.getDeclaringType().getAnAncestor*() instanceof PythonInterpreter and
25+
(
26+
getName().matches("exec%") or
27+
hasName("eval") or
28+
hasName("compile") or
29+
getName().matches("run%")
30+
)
31+
}
32+
}
33+
34+
/** The class `org.python.core.BytecodeLoader`. */
35+
class BytecodeLoader extends RefType {
36+
BytecodeLoader() { this.hasQualifiedName("org.python.core", "BytecodeLoader") }
37+
}
38+
39+
/** Holds if a Jython expression if evaluated, compiled or executed. */
40+
predicate runCode(MethodAccess ma, Expr sink) {
41+
exists(Method m | m = ma.getMethod() |
42+
m instanceof InterpretExprMethod and
43+
sink = ma.getArgument(0)
44+
)
45+
}
46+
47+
/** A method that loads Java class data. */
48+
class LoadClassMethod extends Method {
49+
LoadClassMethod() {
50+
this.getDeclaringType().getAnAncestor*() instanceof BytecodeLoader and
51+
(
52+
hasName("makeClass") or
53+
hasName("makeCode")
54+
)
55+
}
56+
}
57+
58+
/** Holds if a Java class file is loaded. */
59+
predicate loadClass(MethodAccess ma, Expr sink) {
60+
exists(Method m, int i | m = ma.getMethod() |
61+
m instanceof LoadClassMethod and
62+
m.getParameter(i).getType() instanceof Array and // makeClass(java.lang.String name, byte[] data, ...)
63+
sink = ma.getArgument(i)
64+
)
65+
}
66+
67+
/** The class `org.python.core.Py`. */
68+
class Py extends RefType {
69+
Py() { this.hasQualifiedName("org.python.core", "Py") }
70+
}
71+
72+
/** A method that compiles code with `Py`. */
73+
class PyCompileMethod extends Method {
74+
PyCompileMethod() {
75+
this.getDeclaringType().getAnAncestor*() instanceof Py and
76+
getName().matches("compile%")
77+
}
78+
}
79+
80+
/** Holds if source code is compiled with `PyCompileMethod`. */
81+
predicate compile(MethodAccess ma, Expr sink) {
82+
exists(Method m | m = ma.getMethod() |
83+
m instanceof PyCompileMethod and
84+
sink = ma.getArgument(0)
85+
)
86+
}
87+
88+
/** Sink of an expression loaded by Jython. */
89+
class CodeInjectionSink extends DataFlow::ExprNode {
90+
CodeInjectionSink() {
91+
runCode(_, this.getExpr()) or
92+
loadClass(_, this.getExpr()) or
93+
compile(_, this.getExpr())
94+
}
95+
96+
MethodAccess getMethodAccess() {
97+
runCode(result, this.getExpr()) or
98+
loadClass(result, this.getExpr()) or
99+
compile(result, this.getExpr())
100+
}
101+
}
102+
103+
class CodeInjectionConfiguration extends TaintTracking::Configuration {
104+
CodeInjectionConfiguration() { this = "CodeInjectionConfiguration" }
105+
106+
override predicate isSource(DataFlow::Node source) {
107+
source instanceof RemoteFlowSource
108+
or
109+
source instanceof LocalUserInput
110+
}
111+
112+
override predicate isSink(DataFlow::Node sink) { sink instanceof CodeInjectionSink }
113+
114+
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
115+
// @RequestBody MyQueryObj query; interpreter.exec(query.getInterpreterCode());
116+
exists(MethodAccess ma | ma.getQualifier() = node1.asExpr() and ma = node2.asExpr())
117+
}
118+
}
119+
120+
from DataFlow::PathNode source, DataFlow::PathNode sink, CodeInjectionConfiguration conf
121+
where conf.hasFlowPath(source, sink)
122+
select sink.getNode().(CodeInjectionSink).getMethodAccess(), source, sink, "Jython evaluate $@.",
123+
source.getNode(), "user input"

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

Lines changed: 0 additions & 11 deletions
This file was deleted.

0 commit comments

Comments
 (0)