
Эксплойт доказательства концепции для CVE-2025-55182, демонстрирующий неаутентифицированное RCE в React Server Components через небезопасную десериализацию Server Actions, с подробными шагами воспроизведения и рекомендациями по смягчению.
Этот репозиторий содержит PoC-воспроизведение CVE-2025-55182 — критической уязвимости безопасности в React Server Components (RSC), позволяющей неаутентифицированное выполнение произвольного кода.
Уязвимость заключается в том, как React Server Components десериализуют «Server Actions» из запросов клиента. В частности, функция requireModule не проверяла, является ли запрашиваемое имя экспорта прямым свойством модуля. Это позволяло злоумышленникам получить доступ к свойству constructor экспортируемых функций, получая ссылку на глобальный конструктор Function, который можно использовать для выполнения произвольного кода.
Этот PoC использует минимальное окружение Node.js для изоляции уязвимости в библиотеке react-server-dom-webpack, чтобы убедиться, что эксплойт демонстрирует ошибку в самой библиотеке, а не неправильную конфигурацию фреймворка.
npmnpm install
[!ПРИМЕЧАНИЕ] Файл
package.jsonзафиксирован на уязвимой версии19.0.0.
Этот скрипт запускает простой HTTP-сервер, использующий уязвимую среду выполнения React для декодирования запросов.
# tty1
node --conditions react-server server.js
В отдельном терминале запустите эксплойт. Он отправляет вредоносную полезную нагрузку Flight на сервер.
# tty2
node exploit.js id
Вы должны увидеть вывод команды в ответе:
Ожидаемый результат:
Response: uid=0(root) gid=0(root) groups=0(root)
Почему возникла уязвимость?
Функция requireModule в ReactFlightDOMServerNode.js по сути просто доверяла любому name, переданному клиентом. Она выполняла moduleExports[metadata[NAME]] без проверки того, должно ли это свойство быть доступно. Так что если клиент говорил «чувак, дай это свойство», сервер просто отвечал «без проблем! держи, приятель».
Почему позволять доступ к любому свойству — плохая идея?
Потому что это позволяет любому проникнуть в цепочку прототипов, вплоть до constructor, что чрезвычайно опасно. Если модуль экспортирует функцию (например, module.exports = () => {}), то её constructor буквально является глобальным конструктором Function.
Почему получение конструктора Function означает RCE?
Как только злоумышленник получает конструктор Function, он может злоупотребить функцией «Bound Server Action». Они привязывают к нему строку с вредоносным JavaScript (по сути превращая её в new Function("evil code")). И после выполнения сервер выполняет любой код, который они вставили.
Почему React вообще запустит эту вредоносную функцию?
Потому что Server Actions можно запустить по ID. Если злоумышленник создает полезную нагрузку с Action ID, указывающим на их ссылку module#constructor, React разрешает её как обычное действие и выполняет. Это «действие» на самом деле является их вредоносной функцией.
Почему всё это не проверялось?
Система просто предполагала, что id и name из метаданных Server Reference всегда будут ссылаться на действительные экспорты, определённые разработчиком. Не было никакой проверки безопасности, такой как hasOwnProperty, чтобы убедиться, что запрашиваемое свойство является реальным экспортом, а не унаследованным от цепочки прототипов.
server.js, а не Next.js?Я использую сырой server.js (и вспомогательный webpack-runtime.js) для ручной настройки среды выполнения React Server Components. Это позволяет нам:
module.exports = fn)s. Настоящий сборщик может изменить обёртку экспортов в зависимости от конфигурации, но здесь мы этого избегаем.react-server-dom-webpack, а не Next.js.react-server-dom-webpack предполагает, что работает внутри Webpack-сборки. Наш webpack-runtime.js предоставляет ему необходимые глобальные переменные (__webpack_require__, __webpack_chunk_load__).Это не имитация уязвимости, это просто предоставление библиотеке минимально необходимой среды выполнения для фактической работы.
Ведутся обсуждения о «Недействительных PoC», которые работают только если разработчик намеренно выставляет опасные вещи, такие как child_process.exec.
Этот PoC — не из таких. Он работает на нормальной, безопасной установке.
Функция безвредна
Приложение выставляет простую функцию updateProfile, которая просто возвращает строку — ничего подозрительного, никаких команд оболочки.
Эксплойт полностью обходит эту функцию
Уязвимость позволяет злоумышленнику игнорировать безопасный экспорт и перейти сразу к updateProfile.constructor, то есть к глобальному конструктору Function.
Основная проблема — доступ к свойству
React НЕ ДОЛЖЕН был разрешать доступ к .constructor. Разработчик не собирался выставлять конструктор Function, но небезопасная десериализация сделала это за него.
Единственное реальное требование — чтобы модуль экспортировал функцию напрямую (module.exports = fn), что очень распространено в CommonJS и многих сборщиках.
Полезная нагрузка в exploit.js формирует сообщение React Flight с тремя блоками:
id: "user-profile-action#constructor", означающее «дай мне конструктор».bound: указывает на Блок 2, содержащий аргументы.["console.log('nice try, diddy!')"]: строка вредоносного кода.Когда React десериализует это:
user-profile-action..constructor => получает глобальный Function.new Function("console.log('nice try, diddy!')")И это RCE!
Немедленно обновитесь до исправленных версий:
react-server-dom-webpack >= 19.0.1react-server-dom-parcel >= 19.0.1react-server-dom-turbopack >= 19.0.1Исправление вводит проверки hasOwnProperty, чтобы предотвратить доступ к унаследованным свойствам, и ограничивает загрузку файлов base64.
Если вы запустите этот PoC против исправленной версии, сервер упадёт или выдаст ошибку:
$ node --conditions react-server server.js
Listening on http://localhost:3000
/path/to/CVE-2025-55182/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js:2726
resolvedValue = resolvedValue.bind.apply(
^
TypeError: Cannot read properties of undefined (reading 'bind')
at /path/to/CVE-2025-55182/node_modules/react-server-dom-webpack/cjs/react-server-dom-webpack-server.node.development.js:2726:43
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
Node.js v20.19.3
Это подтверждает, что эксплойт не смог получить доступ к свойству constructor (оно вернуло undefined вместо Function), и, следовательно, последующий вызов .bind завершился ошибкой.
Этот код предназначен только для образовательных и тестовых целей. Не используйте этот эксплойт против систем, которыми вы не владеете или на тестирование которых у вас нет явного разрешения.
Выпущено под лицензией DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE.