Skip to content

Commit f41d2a8

Browse files
authored
Merge pull request #16771 from porcupineyhairs/js2py
Python : Arbitrary code execution due to Js2Py
2 parents 8152ec7 + 5ecde38 commit f41d2a8

File tree

7 files changed

+93
-0
lines changed

7 files changed

+93
-0
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<!DOCTYPE qhelp SYSTEM "qhelp.dtd">
2+
<qhelp>
3+
<overview>
4+
<p>
5+
Passing untrusted inputs to a JavaScript interpreter like `Js2Py` can lead to arbitrary
6+
code execution.
7+
</p>
8+
</overview>
9+
<recommendation>
10+
<p> This vulnerability can be prevented either by preventing an untrusted user input to flow
11+
to an <code>eval_js</code> call. Or, the impact of this vulnerability can be
12+
significantly reduced by disabling imports from the interepreted code (note that in a <a
13+
href="https://github.com/PiotrDabkowski/Js2Py/issues/45#issuecomment-258724406">
14+
comment</a> the author of the library highlights that Js2Py is still insecure with this
15+
option).</p>
16+
</recommendation>
17+
<example>
18+
<p>In the example below, the Javascript code being evaluated is controlled by the user and
19+
hence leads to arbitrary code execution.</p>
20+
<sample src="Js2pyBad.py" />
21+
<p>This can be fixed by disabling imports before evaluating the user passed buffer.</p>
22+
<sample src="Js2pyGood.py" />
23+
</example>
24+
</qhelp>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* @name JavaScript code execution.
3+
* @description Passing user supplied arguments to a Javascript to Python translation engine such as Js2Py can lead to remote code execution.
4+
* @problem.severity error
5+
* @security-severity 9.3
6+
* @precision high
7+
* @kind path-problem
8+
* @id py/js2py-rce
9+
* @tags security
10+
* experimental
11+
* external/cwe/cwe-94
12+
*/
13+
14+
import python
15+
import semmle.python.ApiGraphs
16+
import semmle.python.dataflow.new.TaintTracking
17+
import semmle.python.dataflow.new.RemoteFlowSources
18+
import semmle.python.Concepts
19+
20+
module Js2PyFlowConfig implements DataFlow::ConfigSig {
21+
predicate isSource(DataFlow::Node node) { node instanceof RemoteFlowSource }
22+
23+
predicate isSink(DataFlow::Node node) {
24+
API::moduleImport("js2py").getMember(["eval_js", "eval_js6", "EvalJs"]).getACall().getArg(_) =
25+
node
26+
}
27+
}
28+
29+
module Js2PyFlow = TaintTracking::Global<Js2PyFlowConfig>;
30+
31+
import Js2PyFlow::PathGraph
32+
33+
from Js2PyFlow::PathNode source, Js2PyFlow::PathNode sink
34+
where
35+
Js2PyFlow::flowPath(source, sink) and
36+
not exists(API::moduleImport("js2py").getMember("disable_pyimport").getACall())
37+
select sink, source, sink, "This input to Js2Py depends on a $@.", source.getNode(),
38+
"user-provided value"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@bp.route("/bad")
2+
def bad():
3+
jk = flask.request.form["jk"]
4+
jk = eval_js(f"{jk} f()")
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@bp.route("/good")
2+
def good():
3+
# disable python imports to prevent execution of malicious code
4+
js2py.disable_pyimport()
5+
jk = flask.request.form["jk"]
6+
jk = eval_js(f"{jk} f()")
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
edges
2+
| Js2PyTest.py:9:5:9:6 | ControlFlowNode for jk | Js2PyTest.py:10:18:10:28 | ControlFlowNode for Fstring | provenance | |
3+
| Js2PyTest.py:9:10:9:22 | ControlFlowNode for Attribute | Js2PyTest.py:9:5:9:6 | ControlFlowNode for jk | provenance | AdditionalTaintStep |
4+
nodes
5+
| Js2PyTest.py:9:5:9:6 | ControlFlowNode for jk | semmle.label | ControlFlowNode for jk |
6+
| Js2PyTest.py:9:10:9:22 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
7+
| Js2PyTest.py:10:18:10:28 | ControlFlowNode for Fstring | semmle.label | ControlFlowNode for Fstring |
8+
subpaths
9+
#select
10+
| Js2PyTest.py:10:18:10:28 | ControlFlowNode for Fstring | Js2PyTest.py:9:10:9:22 | ControlFlowNode for Attribute | Js2PyTest.py:10:18:10:28 | ControlFlowNode for Fstring | This input to Js2Py depends on a $@. | Js2PyTest.py:9:10:9:22 | ControlFlowNode for Attribute | user-provided value |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
experimental/Security/CWE-094/Js2Py.ql
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
import flask
3+
from js2py import eval_js, disable_pyimport
4+
5+
bp = flask.Blueprint("app", __name__, url_prefix="/")
6+
7+
@bp.route("/bad")
8+
def bad():
9+
jk = flask.request.form["jk"]
10+
jk = eval_js(f"{jk} f()")

0 commit comments

Comments
 (0)