
Детальный технический анализ CVE-2025-55182 — критической неаутентифицированной RCE в Next.js + React 19.0.0. Описывает исследовательский путь, анализ патча, обход прототипа и приемник десериализации Blob, который обеспечивает полную эксплуатацию без специфичных для приложения гаджетов.
Этот документ описывает мой личный исследовательский процесс по CVE-2025-55182, включая подтвержденные находки и проведенные мной эксперименты. Это честный отчет о моем расследовании, включая первоначальные ошибочные предположения и итоговый прорыв.
После тщательного расследования CVE-2025-55182 (CVSS 10.0) мой первоначальный вывод заключался в том, что автоматическое удаленное выполнение кода (RCE) не было публично продемонстрировано, и что эксплуатация требует специфических для приложения гаджетов. Этот вывод был неверен.
5 декабря 2025 года, получив дополнительные сведения от другого независимого исследователя на X (@maple3142), я успешно воспроизвел полное неаутентифицированное RCE на стандартном Next.js без необходимости в каких-либо уязвимостях в коде приложения.
Мой первоначальный фокус был на первом изменении в патче React 19.0.1:
Уязвимая (19.0.0):
return fn.bind.apply(fn, [null].concat(_ref));
Исправленная (19.0.1):
if (Array.isArray(promiseValue)) {
promiseValue = promiseValue.slice(0);
} else {
promiseValue = [];
}
Я предположил, что путь атаки лежит через fn.bind.apply() с вредоносными объектами вместо массивов. Мне удалось продемонстрировать внедрение аргументов в серверные действия с помощью $ACTION_REF_ и контролируемого злоумышленником bound:
curl -X POST http://localhost:9000/ \
-F '$ACTION_REF_0=' \
-F '$ACTION_0:0={"id":"<ACTION_ID>","bound":["; id #","/etc/passwd"]}'
Результат: Аргументы были успешно внедрены в серверное действие. Однако это приводит к RCE только в том случае, если целевая функция использует эти аргументы небезопасно.
Более детальный взгляд на патч выявил ещё одно важное изменение в getOutlinedModel():
Уязвимая:
for (key = 1; key < reference.length; key++)
parentObject = parentObject[reference[key]];
Исправленная:
if (hasOwnProperty.call(value, name)) {
value = value[name];
}
Это уязвимое поведение позволяло обход цепочки прототипов с помощью ссылок вида:
$1:__proto__:constructor:constructor
В ходе исследования я протестировал thenable-объект, содержащий свойство .then:
{"then": "$1:__proto__:constructor:constructor"}
Когда JavaScript обрабатывает это через await:
.then и считает объект Promiseobj.then(resolve, reject)then разрешается в Function.constructor, JavaScript пытается выполнить Function(resolve, reject)Наблюдаемый результат:
SyntaxError: Unexpected token 'function'
at Object.Function [as then] (<anonymous>)
Когда Function.constructor вызывается как:
Function(resolve, reject)
// resolve.toString() = "function () { [native code] }"
// Function пытается распарсить это как код → SyntaxError
Аргументы resolve и reject всегда являются нативными функциями Promise. Function пытается интерпретировать первый аргумент как исходный код, который является недопустимым JavaScript.
На этом мое исследование застопорилось. Я пришел к выводу, что управление аргументами Function.constructor невозможно без специфичного для приложения гаджета.
После публикации моих первоначальных находок другой независимый исследователь указал мне на критический элемент, который я упустил: сток десериализации $B (Blob).
В скомпилированном серверном коде React Flight (невидимом в исходниках TypeScript) существует:
case "B":
return response._formData.get(response._prefix + id);
Расположение:
[email protected]cjs/react-server-dom-webpack-server.node.unbundled.development.js[email protected]/dist/compiled/react-server-dom-webpack/Этот код позволяет React вызывать response._formData.get() со значениями, полученными из контролируемого злоумышленником ввода, без валидации.
| Подход | Аргументы для Function.constructor | Результат |
|---|---|---|
| Thenable (Этап 3) | resolve, reject (нативные функции) | ❌ SyntaxError |
| Blob + Отравленный Response | _prefix (контролируемая злоумышленником строка) | ✅ RCE |
Комбинируя:
$1:__proto__:then → Chunk.prototype.then)_response с:
_formData.get, установленный на Function.constructor_prefix, установленный на произвольный JavaScript-код$BОбработчик case "B": выполняет:
Function.constructor("<код атакующего>" + id)
Это полностью обходит ограничение привязки аргументов.
Популярные GitHub PoC, утверждающие о RCE, используют Action ID, такие как:
"child_process#execSync""vm#runInThisContext"Это подделка. Next.js принимает только Action ID, определённые приложением. Неверные ID приводят к:
TypeError: Cannot read properties of undefined (reading 'workers')
Однако настоящая эксплойтация не требует поддельных Action ID. Любой валидный ID серверного действия работает.
Я протестировал эту цепочку эксплойтации на минимальном приложении Next.js 15.0.3 + React 19.0.0 только с:
async function myAction(data) {
"use server";
console.log("Server Action called with:", data);
return { success: true, received: data };
}
Результат: Полное RCE подтверждено. Приложение не содержало никакого небезопасного кода, ни eval, ни execSync, ни гаджетов.
| Аспект | Результат |
|---|---|
| Требуется аутентификация? | ❌ Нет |
| Требуется гаджет приложения? | ❌ Нет |
| Работает на стандартном Next.js? | ✅ Да |
| Количество необходимых запросов | 1 POST |
| Затронутые версии | Next.js ≤15.0.4 + React 19.0.0 |
| Оценка CVSS | 10.0 (обоснованно) |
По состоянию на 5 декабря 2025 года сток $B всё ещё существует в Next.js 15.0.5 (якобы исправленной версии).
Проверка:
$ npm pack [email protected]
$ tar -xzf next-15.0.5.tgz
$ grep -A5 'case "B":' package/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js
Результат:
case "B":
return response._formData.get(response._prefix + obj);
Код идентичен уязвимой версии.
В связи с обнаружением того, что уязвимость может быть не полностью исправлена в заявленных «исправленных» версиях, я удерживаю полный proof-of-concept эксплойт до проверки с командами безопасности Vercel и Meta.
Технические детали, представленные в этом документе, достаточны для понимания механизма уязвимости, но намеренно неполны, чтобы предотвратить немедленную эксплуатацию.
| Техника | Статус |
|---|---|
Внедрение аргументов через bound | ✅ Подтверждено (ограниченное воздействие) |
| Обход прототипа | ✅ Подтверждено |
Доступ к Function.constructor через thenable | ✅ Подтверждено (но не эксплуатируется самостоятельно) |
| Обнаружение уязвимых версий | ✅ Подтверждено |
| Проблема | Воздействие |
|---|---|
Сток десериализации $B (Blob) | ❌ Критично — позволяет контролировать аргументы |
| Изучение скомпилированного кода вместо исходников | ❌ Сток существует только в скомпилированном выводе |
| Механизм отравления объекта Response | ❌ Позволяет обойти все защиты |
CVE-2025-55182 эксплуатируема для полного неаутентифицированного RCE на стандартных приложениях Next.js.
$B от независимого исследователя$B по-прежнему произвольные объекты _responseNext-Action$@, __proto__, $B$B был предоставлен исследователем на X (@maple3142)Последнее обновление: 5 декабря 2025 г.