Skip to content
KitploitKITPLOIT
ToolsBlog
Log in
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2024-28397-js2py-Sandbox-Escape — Detailed analysis of CVE-2024-28397, a sandbox escape vulnerability in js2py enabling RCE via Python object traversal. Includes code analysis, PoC, and fix recommendations for defensive research. | Kitploit
Tools/GitHubGitHub/naved124/cve-2024-28397-js2py-sandbox-escape
Vulnerability AnalysisCode AnalysisExploitationWeb SecurityPapers & ResearchLearning & EducationPayload DevelopmentBinary Exploitation
GitHub
naved124/cve-2024-28397-js2py-sandbox-escape

CVE-2024-28397-js2py-Sandbox-Escape

Detailed analysis of CVE-2024-28397, a sandbox escape vulnerability in js2py enabling RCE via Python object traversal. Includes code analysis, PoC, and fix recommendations for defensive research.

View Repository
71 year agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2024-28397-js2py-Sandbox-Escape

js2py Vulnerability Analysis

Introduction

js2py is a popular python package that can evaluate javascript code inside the python interpreter. It is used by various web scrapers to parse javascript code on websites.

There exists a vulnerability in the implementation of a global variable inside js2py, allowing an attacker to obtain a reference to a python object in the js2py environment, thus enabling the attacker to escape the JS environment and execute arbitrary commands on the host.

Normally, a user would call js2py.disable_pyimport() to stop JavaScript code from escaping the js2py environment. But with this vulnerability, an attacker can evade this restriction and execute any command on the target host.

The threat actor can host a website containing a malicious JavaScript file or send a malicious script via HTTP API for the victim to parse. By doing that, the actor can achieve remote code execution on the host by executing arbitrary shell commands on the target.


Preface

js2py is a Python library commonly used in crawlers, used to parse and execute JS code in a native Python environment. Crawlers generally use js2py to parse JS code obtained from the web, thereby simulating the browser environment.

However, js2py has a function that is extremely dangerous for crawlers: it supports importing and using Python packages in JS, which means js2py allows JS code to manipulate various Python libraries and directly interact with the Python environment. Precisely because of this, we can use a method similar to Jinja SSTI, and in the js2py environment use a Python object to find the subprocess.Popen class to achieve RCE.

Moreover, since js2py is a package from the Python2 era, widely used and long unmaintained, it should be relatively easy to analyze.


Code Analysis

JS Code to Python Code

After setting breakpoints, it can be found that the place where the JS code is actually parsed is the Eval function of host/jseval.py. By setting breakpoints in it, you can see the Python code converted by js2py.

For example, this piece of JS code:

let a = 114
console.log(a)

In the end, will be parsed into this piece of Python code:

var.registers(['a'])
var.put('a', Js(114.0))
EVAL_RESULT = (var.get('console').callprop('log', var.get('a')))

It can be seen that variables at the JS layer are all stored in the Python variable var. All JS-layer values are cleanly stored as the PyJs class (here Js is actually a function, explained later). Functions are also called through callprop. Under normal circumstances, JS code cannot touch Python objects.

When looking at the code, it was noticed that the author really liked to use string concatenation to construct the final Python code, so the thought arose whether JS code could be constructed to generate illegal Python code, thereby constructing arbitrary Python code and executing it. But considering this path is much more difficult than the later path, it was not further explored.


Python Data to JS Data

To obtain a Python object and implement RCE, the first thing to see, of course, is how Python objects are converted into PyJs objects.

First, locate the implementation of the Js function, in base.py. The role of the Js function is to convert the incoming Python value into the corresponding PyJs value, thereby allowing JS code to manipulate these values.

def Js(val, Clamped=False):
    '''Converts Py type to PyJs type'''
    if isinstance(val, PyJs):
        return val
    elif val is None:
        return undefined
    elif isinstance(val, basestring):
        return PyJsString(val, StringPrototype)
    elif isinstance(val, bool):
        return true if val else false
    elif isinstance(val, float) or isinstance(val, int) or isinstance(
            val, long) or (NUMPY_AVAILABLE and isinstance(
                val,
                (numpy.int8, numpy.uint8, numpy.int16, numpy.uint16,
                 numpy.int32, numpy.uint32, numpy.float32, numpy.float64))):
        # This is supposed to speed things up. may not be the case
        if val in NUM_BANK:
            return NUM_BANK[val]
        return PyJsNumber(float(val), NumberPrototype)
    ... # several lines omitted here
    else:  # try to convert to js object
        return py_wrap(val)

It can be seen that Python’s basic data structures such as bool, float, list, etc. will be converted into dedicated PyJs classes, while other types of data will be handled by py_wrap, and finally become PyObjectWrapper class.

Ordinary PyJs classes represent numbers, booleans, and other common data, while PyObjectWrapper represents special data such as Python modules. So as long as we obtain a PyObjectWrapper type of data, we can use an attribute access method similar to Jinja SSTI to achieve RCE.

Generally speaking, PyObjectWrapper type data can only be obtained when the function of importing Python packages is enabled, but because js2py is long unmaintained and did not carefully consider the differences between Python2 and Python3, a sandbox escape vulnerability ultimately occurred.

Insert a side note: When looking at the implementation of PyJs, I saw the author wrote these lines of code:

if six.PY3:
    PyJs.__hash__ = PyJs._fuck_python3
    PyJs.__truediv__ = PyJs.__div__

It can be said that the author extremely dislikes Python3.


JS Function Implementation

While js2py provides the function of converting JS code to Python code, it also provides multiple built-in objects such as console and Object to support the normal running of JS code.

Our ultimate goal is to bypass the restriction of pyimport and obtain a PyObjectWrapper object. From the analysis above, it can be seen that to get a PyObjectWrapper object out of nothing, we can only start from the implementation of built-in objects and extract a PyObjectWrapper object from them.

Begin scanning the implementation code of built-in objects. From constructors/jsobject.py, you can see the implementation of various functions in the Object object, including commonly used functions like Object.keys.

Then, you can see this function:

def getOwnPropertyNames(obj):
    if not obj.is_object():
        raise MakeError(
            'TypeError',
            'Object.getOwnPropertyDescriptor called on non-object')
    return obj.own.keys()

js2py uses dict to represent objects in JS. The keys() here calls the Python dictionary’s keys(). Anyone who has studied Python should know that in Python2, this function returns a list, while in Python3 it returns a dict_keys view. According to the above implementation of the Js function, this dict_keys will be converted into PyObjectWrapper, and thus we can achieve RCE.


Achieving RCE

First verify whether getOwnPropertyNames can obtain a PyObjectWrapper:

import js2py

code = """
let a = Object.getOwnPropertyNames({})
console.log(a)
"""

js2py.eval_js(code)

It printed PyObjectWrapper(dict_keys([])), of course it can.

Then, based on this object, we obtain the __getattribute__ function, and we can easily achieve RCE. When writing the PoC, it was thought of too complicated; actually, just using __class__.__base__ is enough to obtain the __getattribute__ function.

Then, based on the __getattribute__ function, obtain the object object, and then write a recursive function to find any class of any module. Here, for RCE, what is sought is subprocess.Popen.

NOTE (sanitized): The code snippet below is shown for educational and research purposes. It has been sanitized to avoid facilitating misuse: sensitive shell commands and direct system execution have been replaced with placeholders. Do not run this against production or unpatched systems.

import js2py
Download Tool