mirror of
https://github.com/github/codeql.git
synced 2026-04-25 08:45:14 +02:00
Python : Arbitrary codde execution due to Js2Py
Js2Py is a Javascript to Python translation library written in Python. It allows users to invoke JavaScript code directly from Python. The Js2Py interpreter by default exposes the entire standard library to it's users. This can lead to security issues if a malicious input were directly. This PR includes a CodeQL query along with a qhelp and testcases to detect cases where an untrusted input flows to an Js2Py eval call. This query successfully detects CVE-2023-0297 in `pyload/pyload`along with it's fix. The databases can be downloaded from the links bellow. ``` https://file.io/qrMEjSJJoTq1 https://filetransfer.io/data-package/a02eab7V#link ```
This commit is contained in:
24
python/ql/src/experimental/Security/CWE-094/Js2Py.qhelp
Normal file
24
python/ql/src/experimental/Security/CWE-094/Js2Py.qhelp
Normal file
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE qhelp SYSTEM "qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Passing untrusted inputs to a JavaScript interpreter like `Js2Py` can lead to arbitrary
|
||||
code execution.
|
||||
</p>
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p> This vulnerability can be prevented either by preventing an untrusted user input to flow
|
||||
to an <code>eval_js</code> call. Or, the impact of this vulnerability can be
|
||||
significantly reduced by disabling imports from the interepreted code (note that in a <a
|
||||
href="https://github.com/PiotrDabkowski/Js2Py/issues/45#issuecomment-258724406">
|
||||
comment</a> the author of the library highlights that Js2Py is still insecure with this
|
||||
option).</p>
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>In the example below, the Javascript code being evaluated is controlled by the user and
|
||||
hence leads to arbitrary code execution.</p>
|
||||
<sample src="Js2PyBad.py" />
|
||||
<p>This can be fixed by disabling imports before evaluating the user passed buffer.
|
||||
<sample src="Js2PyGood.py" />
|
||||
</example>
|
||||
</qhelp>
|
||||
36
python/ql/src/experimental/Security/CWE-094/Js2Py.ql
Normal file
36
python/ql/src/experimental/Security/CWE-094/Js2Py.ql
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* @name JavaScript code execution.
|
||||
* @description Passing user supplied arguments to a Javascript to Python translation engine such as Js2Py can lead to remote code execution.
|
||||
* @severity high
|
||||
* @kind path-problem
|
||||
* @id py/js2py-rce
|
||||
* @tags security
|
||||
* experimental
|
||||
* external/cwe/cwe-94
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.ApiGraphs
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import semmle.python.dataflow.new.RemoteFlowSources
|
||||
import semmle.python.Concepts
|
||||
|
||||
module Js2PyFlowConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node node) { node instanceof RemoteFlowSource }
|
||||
|
||||
predicate isSink(DataFlow::Node node) {
|
||||
API::moduleImport("js2py").getMember(["eval_js", "eval_js6", "EvalJs"]).getACall().getArg(_) =
|
||||
node
|
||||
}
|
||||
}
|
||||
|
||||
module Js2PyFlow = TaintTracking::Global<Js2PyFlowConfig>;
|
||||
|
||||
import Js2PyFlow::PathGraph
|
||||
|
||||
from Js2PyFlow::PathNode source, Js2PyFlow::PathNode sink
|
||||
where
|
||||
Js2PyFlow::flowPath(source, sink) and
|
||||
not exists(API::moduleImport("js2py").getMember("disable_pyimport").getACall())
|
||||
select sink, source, sink, "This input to Js2Py depends on a $@.", source.getNode(),
|
||||
"user-provided value"
|
||||
4
python/ql/src/experimental/Security/CWE-094/Js2pyBad.py
Normal file
4
python/ql/src/experimental/Security/CWE-094/Js2pyBad.py
Normal file
@@ -0,0 +1,4 @@
|
||||
@bp.route("/bad")
|
||||
def bad():
|
||||
jk = flask.request.form["jk"]
|
||||
jk = eval_js(f"{jk} f()")
|
||||
6
python/ql/src/experimental/Security/CWE-094/Js2pyGood.py
Normal file
6
python/ql/src/experimental/Security/CWE-094/Js2pyGood.py
Normal file
@@ -0,0 +1,6 @@
|
||||
@bp.route("/good")
|
||||
def good():
|
||||
# disable python imports to prevent execution of malicious code
|
||||
js2py.disable_pyimport()
|
||||
jk = flask.request.form["jk"]
|
||||
jk = eval_js(f"{jk} f()")
|
||||
@@ -0,0 +1,10 @@
|
||||
edges
|
||||
| Js2PyTest.py:9:5:9:6 | ControlFlowNode for jk | Js2PyTest.py:10:18:10:28 | ControlFlowNode for Fstring | provenance | |
|
||||
| Js2PyTest.py:9:10:9:22 | ControlFlowNode for Attribute | Js2PyTest.py:9:5:9:6 | ControlFlowNode for jk | provenance | AdditionalTaintStep |
|
||||
nodes
|
||||
| Js2PyTest.py:9:5:9:6 | ControlFlowNode for jk | semmle.label | ControlFlowNode for jk |
|
||||
| Js2PyTest.py:9:10:9:22 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| Js2PyTest.py:10:18:10:28 | ControlFlowNode for Fstring | semmle.label | ControlFlowNode for Fstring |
|
||||
subpaths
|
||||
#select
|
||||
| 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 can lead to arbitrary code execution |
|
||||
@@ -0,0 +1 @@
|
||||
experimental/Security/CWE-094/Js2Py.ql
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
import flask
|
||||
from js2py import eval_js, disable_pyimport
|
||||
|
||||
bp = flask.Blueprint("app", __name__, url_prefix="/")
|
||||
|
||||
@bp.route("/bad")
|
||||
def bad():
|
||||
jk = flask.request.form["jk"]
|
||||
jk = eval_js(f"{jk} f()")
|
||||
Reference in New Issue
Block a user