Python : Arbitrary code execution due to Js2Py#16771
Conversation
|
Hello porcupineyhairs 👋 In the meantime, feel free to make changes to the pull request. If you'd like to maximize payout for your this and future submissions, here are a few general guidelines, that we might take into consideration when reviewing a submission.
Please note that these are guidelines, not rules. Since we have a lot of different types of submissions, the guidelines might vary for each submission. Happy hacking! |
|
@github/codeql-python 👋 This submission is ready for review. |
RasmusWL
left a comment
There was a problem hiding this comment.
Thanks for your contribution 💪
I've made a few suggestions. If you accept the one for the alert message, you will also need to update the .expected files.
I had a brief look to understand what js2py.disable_pyimport() actually does (src), and found an interesting comment highlighting that even when using this, you should still consider using the library with untrusted input as insecure. I think this is important to highlight in the qhelp.
| </p> | ||
| </overview> | ||
| <recommendation> | ||
| <p> This vulnerability can be prevented either by preventing an untrusted user input to lfow |
There was a problem hiding this comment.
| <p> This vulnerability can be prevented either by preventing an untrusted user input to lfow | |
| <p> This vulnerability can be prevented either by preventing an untrusted user input to flow |
| </overview> | ||
| <recommendation> | ||
| <p> This vulnerability can be prevented either by preventing an untrusted user input to lfow | ||
| to an <code>eval_js</code> call. Or, thee impact of this vulnerability can be |
There was a problem hiding this comment.
| to an <code>eval_js</code> call. Or, thee impact of this vulnerability can be | |
| to an <code>eval_js</code> call. Or, the impact of this vulnerability can be |
| * @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 python/js2py-rce |
There was a problem hiding this comment.
convention is to use py/ as the prefix
| * @id python/js2py-rce | |
| * @id py/js2py-rce |
| private API::Node js2py() { result = API::moduleImport("js2py") } | ||
|
|
||
| private API::Node js2evaljs() { result = js2py().getMember(["eval_js", "eval_js6"]) } | ||
|
|
||
| private class DisablePyImportCall extends API::CallNode, DataFlow::CallCfgNode { | ||
| DisablePyImportCall() { this = js2py().getMember("disable_pyimport").getACall() } | ||
| } | ||
|
|
||
| class EvalJsCall extends API::CallNode, DataFlow::CallCfgNode { | ||
| EvalJsCall() { this = js2evaljs().getACall() } | ||
| } | ||
|
|
||
| module Js2PyFlowConfiguration implements DataFlow::ConfigSig { | ||
| predicate isSource(DataFlow::Node node) { node instanceof RemoteFlowSource } | ||
|
|
||
| predicate isSink(DataFlow::Node node) { exists(EvalJsCall e | e.getArg(_) = node) } | ||
| } | ||
|
|
||
| module Js2PyFlow = TaintTracking::Global<Js2PyFlowConfiguration>; | ||
|
|
||
| import Js2PyFlow::PathGraph | ||
|
|
||
| from Js2PyFlow::PathNode source, Js2PyFlow::PathNode sink | ||
| where | ||
| Js2PyFlow::flowPath(source, sink) and | ||
| not exists(DisablePyImportCall c) |
There was a problem hiding this comment.
I think the following looks cleaner, although it does the same.
NOTE: I've also added support for EvalJs method call.
| private API::Node js2py() { result = API::moduleImport("js2py") } | |
| private API::Node js2evaljs() { result = js2py().getMember(["eval_js", "eval_js6"]) } | |
| private class DisablePyImportCall extends API::CallNode, DataFlow::CallCfgNode { | |
| DisablePyImportCall() { this = js2py().getMember("disable_pyimport").getACall() } | |
| } | |
| class EvalJsCall extends API::CallNode, DataFlow::CallCfgNode { | |
| EvalJsCall() { this = js2evaljs().getACall() } | |
| } | |
| module Js2PyFlowConfiguration implements DataFlow::ConfigSig { | |
| predicate isSource(DataFlow::Node node) { node instanceof RemoteFlowSource } | |
| predicate isSink(DataFlow::Node node) { exists(EvalJsCall e | e.getArg(_) = node) } | |
| } | |
| module Js2PyFlow = TaintTracking::Global<Js2PyFlowConfiguration>; | |
| import Js2PyFlow::PathGraph | |
| from Js2PyFlow::PathNode source, Js2PyFlow::PathNode sink | |
| where | |
| Js2PyFlow::flowPath(source, sink) and | |
| not exists(DisablePyImportCall c) | |
| module Js2PyFlowConfiguration 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<Js2PyFlowConfiguration>; | |
| 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()) |
| where | ||
| Js2PyFlow::flowPath(source, sink) and | ||
| not exists(DisablePyImportCall c) | ||
| select sink, source, sink, "This can lead to arbitrary code execution" |
There was a problem hiding this comment.
This alert message is more in-line with our other alert messages
| select sink, source, sink, "This can lead to arbitrary code execution" | |
| select sink, source, sink, "This input to Js2Py depends on a $@.", source.getNode(), | |
| "user-provided value" |
| <recommendation> | ||
| <p> This vulnerability can be prevented either by preventing an untrusted user input to lfow | ||
| to an <code>eval_js</code> call. Or, thee impact of this vulnerability can be | ||
| significantly reduced by disabling imports from the interepreted code. </p> |
There was a problem hiding this comment.
| significantly reduced by disabling imports from the interepreted code. </p> | |
| 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> |
|
QHelp previews: python/ql/src/experimental/Security/CWE-094/Js2Py.qhelpJavaScript code execution.Passing untrusted inputs to a JavaScript interpreter like `Js2Py` can lead to arbitrary code execution. RecommendationThis vulnerability can be prevented either by preventing an untrusted user input to flow to an ExampleIn the example below, the Javascript code being evaluated is controlled by the user and hence leads to arbitrary code execution. @bp.route("/bad")
def bad():
jk = flask.request.form["jk"]
jk = eval_js(f"{jk} f()")This can be fixed by disabling imports before evaluating the user passed buffer. @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()") |
|
@RasmusWL Changes done! |
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 ```
|
I would strongly advise to just push additional commits to your branch/PR next time. By doing so, it becomes very easy for me to see the changes you made after reading my review. With the force push, I have to remember what the code looked like before and try to figure out what has changed 🤔 |
|
@RasmusWL Sorry about that. I made a couple of mistakes while merging so I had to force push again. |
RasmusWL
left a comment
There was a problem hiding this comment.
Thanks 👍
Before promoting this, I think we should expand tests a bit to also include js2py.disable_pyimport()
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/pyloadalong with it's fix. The databases can be downloaded from the links bellow.