
RCE до аутентификации в React Server Components версий 19.0.0, 19.1.0, 19.1.1 и 19.2.0.
Этот репозиторий содержит воспроизведение 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
[!NOTE] В
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("вредоносный код")). И как только это выполняется, сервер выполняет любой код, который злоумышленник туда поместил.
Почему React вообще запустит эту вредоносную функцию?
Потому что Серверные действия можно вызывать по 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). Настоящий сборщик может изменить способ обёртки экспортов в зависимости от своей конфигурации.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, которая просто возвращает строку и ничего подозрительного, никаких shell-команд.
Эксплойт полностью обходит эту функцию
Уязвимость позволяет атакующему игнорировать безопасный экспорт и перейти сразу к 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.