React 19 漏洞分析(CVE-2025-55182)
CVE-2025-55182(圈内已经叫它 React2Shell)是 React 19 的 Server Components / Server Functions 协议(Flight)里的反序列化 + 服务器端原型污染漏洞,在默认配置下就能被 未认证远程攻击者直接打成 RCE(远程代码执行),而且已经在野外大规模利用,被 CISA 拉进 KEV 列表,CVSS 评分 10 分满分。(React)
1. 这个漏洞到底是什么 & 影响范围
1.1 本质概括
一句话:RSC Flight 协议在服务端“解码客户端发来的 JSON”时,做了不安全的反序列化 / 合并,导致攻击者可以通过构造特殊 payload 污染内部对象的原型,最后让 Node 进程执行任意 JS / shell 命令。
官方 / NVD 的描述要点:(React)
- 漏洞存在于 React Server Components 19.0.0、19.1.0、19.1.1、19.2.0 中:
react-server-dom-webpackreact-server-dom-parcelreact-server-dom-turbopack
- 类型:反序列化不可信数据(CWE-502),兼具 Server-side prototype pollution 特征。(nvd.nist.gov)
- 攻击面:只要你的应用支持 RSC / Server Actions,对外暴露了对应 Endpoint,就有可能被打,即使你自己没写任何自定义 Server Function。(React)
- 攻击条件:
- 无需登录(pre-auth)
- 只需要构造一次 HTTP 请求
- 默认配置的 Next.js + App Router,新建空工程
create-next-app+ build + start 就能被利用。(wiz.io)
1.2 受影响的框架 / 版本(和你日常会用到的)
根据 React 官方 + Vercel / Wiz / NVD:(React)
React 相关包:
- 有问题的:
react-server-dom-*19.0.0 / 19.1.0 / 19.1.1 / 19.2.0
- 已修复版本:
19.0.1,19.1.2,19.2.1
Next.js(App Router)(实际上大部分人真正被打的都是它):(React)
- 受影响:
- 15.0.x < 15.0.5
- 15.1.x < 15.1.9
- 15.2.x < 15.2.6
- 15.3.x < 15.3.6
- 15.4.x < 15.4.8
- 15.5.x < 15.5.7
- 16.0.x < 16.0.7
- 以及部分 14.3.0 之后的 canary(14.3.0-canary.77 及之后)
- 已修复:
- 15.0.5 / 15.1.9 / 15.2.6 / 15.3.6 / 15.4.8 / 15.5.7 / 16.0.7
- 或退回最新稳定 14.x
其他受影响生态:
- React Router RSC 预览 +
@vitejs/plugin-rsc - Waku、Redwood SDK、Parcel RSC 等 RSC 插件生态(React)
攻击在野情况:
- 已被 CISA 标记为「已知被利用漏洞」;政府机构要求 2025-12-26 前完成修复。(nvd.nist.gov)
- AWS、Wiz、Datadog 均已观测到大规模扫描和实际入侵(挖矿、窃取云凭据、植入后门等)。(wiz.io)
2. 攻击原理 & 黑客是怎么利用的(含代码级解释)
2.1 RSC Flight 协议大致怎么跑
简化一下 React Server Components + Server Actions 的服务端处理过程(伪代码):
// 极度简化的示意,非真实源码
async function handleRscRequest(req: IncomingMessage) {
const flightPayload = await readStreamAsRscChunks(req); // 来自客户端
const model = decodeRscPayload(flightPayload); // ⬅ 漏洞在这里附近
const action = resolveServerAction(model); // 找到要执行的 server function
const result = await action(...model.args);
const responseStream = encodeRscResponse(result);
responseStream.pipe(res);
}
关键点:
flightPayload是客户端发来的、特殊格式的 JSON / multipart 数据(所谓 Flight 协议)。- 在
decodeRscPayload里,React 会:- 反序列化 JSON
- 根据一些约定字段(比如
status,value,_response等)构造内部数据结构 - 在某些地方对对象做 merge / 填充
问题就出在:这些字段里有一部分是直接来自客户端的,合并时又缺乏足够的校验和原型保护。(wiz.io)
2.2 服务器端原型污染(Datadog 的简化示例)
Datadog 用了一个抽象示例来说明「服务器端原型污染如何变成任意代码执行」,这个例子和本次 CVE 的利用模型高度接近:(securitylabs.datadoghq.com)
// 一个“看起来很正常”的深度 merge
function merge(target, source) {
for (let key in source) {
if (typeof source[key] === 'object' && source[key] !== null) {
target[key] = target[key] || {};
merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
const userInput = JSON.parse(process.argv[2] || '{}');
const defaultConfig = {};
const config = merge(defaultConfig, userInput);
// 后面用 config 当作 spawnSync 的 options
const result = spawnSync('sh', [], /* 想象这里用的是 config */ {});
- 如果
userInput是{"__proto__": { "input": "恶意命令" }},
那么merge会不小心把Object.prototype.input也改掉(__proto__是特殊键)。 - 后续调用
spawnSync('sh', [], {} )等价于spawnSync('sh', [], { input: '恶意命令' }),命令就被执行了。
这就是「反序列化不可信对象 + 原型污染 ——> 远程代码执行」的典型路线。
RSC 这次本质上就是类似的事情,只不过:
- 参与合并的对象,是 React 内部用于处理 RSC 请求的各种 “record / response / chunk” 对象;
- 最终被污染利用的,是这些对象在调用
then/constructor/require等内部路径时的行为; - 结果是攻击者可以让 Node 进程执行类似
require("child_process").exec(/* 攻击命令 */)这一类的东西。(securitylabs.datadoghq.com)
2.3 这次漏洞中发生了什么(高层次还原)
结合 Wiz / Datadog 的技术分析,可以抽象出这样的过程:(wiz.io)
- 攻击者发现目标使用 RSC / Next.js App Router
- 扫描响应头 / 路由结构(如存在
Next-Action头、/_rsc//_actions等特征)。
- 扫描响应头 / 路由结构(如存在
- 构造一个特殊的 Flight JSON / multipart 请求:
- 在某个模型 / chunk 中注入类似:
- 带有
__proto__、constructor等特殊键 - 带有
then字段,让对象被当成 Promise / thenable - 带有
_response、_prefix等会在内部被拼接执行的字段
- 带有
- 在某个模型 / chunk 中注入类似:
- React 服务端在 decode 时:
- 把客户端发来的对象 merge 进内部记录对象;
- 没有过滤
__proto__/constructor等危险键;
- 由于原型被污染 / 字段被注入:
record.value.then实际引用的是攻击者注入的函数;- 这个 then 函数内部通过
process.mainModule.require("child_process").execSync(...)等手段执行任意命令; - 命令的输出再被塞回 RSC 流 / 用作跳转参数等,以便攻击者确认利用成功、继续后渗透。
或者把某些字段当作 thenable 调用:
// 示意:status==='resolved_model' 时去调用 .then
if (record.status === 'resolved_model') {
record.value.then(/*...*/);
}
某些逻辑会像这样「信任」这些字段:
// 示意:把 _response 对象里的内容挂到自身
Object.assign(record._response, parsed._response);
// 之后某处调用
if (record._response && record._response._prefix) {
someBuffer += record._response._prefix;
// ...
}
Datadog、Wiz 等厂家在野外观察到的真实 payload 后续行为包括:
- 读取
.env、next.config.js、/etc/passwd等敏感文件; - 尝试获取 AWS / GCP 等云元数据、环境变量中的访问密钥;
- 下载并执行 XMRig / 各种挖矿脚本;
- 注入恶意 HTTP handler 作为持久后门。(wiz.io)
2.4 为啥说是「React2Shell」?
- 利用链基本是:HTTP 请求 → RSC 解码 → 原型污染 / 逻辑漏洞 → child_process / vm.runInThisContext → Shell / 任意 JS。
- 更糟糕的是:默认配置,无需开发者写任何额外代码,就可以打下一台 Next.js / RSC 服务器。(wiz.io)
3. 如何修复 & 防范(框架级 + 代码级)
3.1 首要措施:立刻升级依赖
React / RSC 包:(React)
# 建议直接统一升到当前最新稳定版本
npm install react@latest react-dom@latest
# 使用 Webpack RSC
npm install react-server-dom-webpack@latest
# 使用 Vite RSC 插件
npm install @vitejs/plugin-rsc@latest
# 使用 Parcel RSC
npm install react-server-dom-parcel@latest
# 使用 Turbopack RSC
npm install react-server-dom-turbopack@latest
Next.js(App Router):(React)
# 按你所在的版本线升级到 patched 版本
npm install next@15.0.5 # 15.0.x
npm install next@15.1.9 # 15.1.x
npm install next@15.2.6 # 15.2.x
npm install next@15.3.6 # 15.3.x
npm install next@15.4.8 # 15.4.x
npm install next@15.5.7 # 15.5.x
npm install next@16.0.7 # 16.0.x
# 如果你在用 14.3.0-canary.77+ 这些 canary:
npm install next@14 # 退回 14.x 稳定版
Vercel 还提供了一个 CLI 工具帮你对 Next 工程打补丁:
npx fix-react2shell-next
但以官方升级公告为准,CLI 工具只是辅助。(nextjs.org)
3.2 暂时缓解(在无法马上升级时)
行业建议的临时缓解手段:(wiz.io)
- 禁用 / 限制 RSC / Server Actions:
- Next.js 中可以临时关闭 App Router 的 RSC 功能(如迁回 Pages Router,或移除 Server Actions)。
- WAF / 反向代理层做过滤:
- 阻断带有
Next-Action头的请求,或仅在内部受信网段允许此类请求。 - 拦截可疑字段(比如 RSC 标志性的 multipart 字段名)——这个需要配合安全团队谨慎定制规则。
- 阻断带有
- 最小暴露面:
- 不要让内部管理 / API 端口直接对公网开放;
- 对公网使用专门的入口网关,只放必要路由,不裸暴露整个 Node 进程。
但这些都是“止血”方案,真正的修复还是升级依赖。
3.3 代码级防御思路(你自己的 Node / React 代码可以做什么)
虽然这次是 React 内部代码的锅,但从工程实践来说,可以在自己写的东西里避免类似坑:
3.3.1 不要无脑 merge 不可信对象
反例(类似 Datadog 举的例子):
// ❌ 反序列化 + 深度 merge,没做任何校验
function deepMerge(target, source) {
for (const key in source) {
if (source[key] && typeof source[key] === 'object') {
if (!target[key]) target[key] = {};
deepMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
const payload = JSON.parse(req.body); // 来自客户端
const actionRecord = {};
deepMerge(actionRecord, payload); // ⬅ 潜在原型污染
更安全的写法:
// ✅ 显式白名单 + 禁止原型键
const BANNED_KEYS = ['__proto__', 'constructor', 'prototype'];
function assertNoPrototypeKeys(obj: unknown) {
if (typeof obj !== 'object' || obj === null) return;
for (const key of Object.keys(obj)) {
if (BANNED_KEYS.includes(key)) {
throw new Error(`Prototype pollution key detected: ${key}`);
}
// 递归检查子对象
// @ts-ignore
assertNoPrototypeKeys((obj as any)[key]);
}
}
type ActionPayload = {
status: 'resolved_model' | 'pending' | 'rejected';
value?: unknown;
reason?: unknown;
// 只允许你自己定义的少数字段
};
function safeDecodePayload(raw: string): ActionPayload {
const parsed = JSON.parse(raw);
assertNoPrototypeKeys(parsed); // 先 ban 掉原型相关 key
// 显式拣字段(而不是把整个对象 assign 进去)
const { status, value, reason } = parsed as Record<string, unknown>;
if (status !== 'resolved_model' &&
status !== 'pending' &&
status !== 'rejected') {
throw new Error('Invalid status');
}
return { status, value, reason };
}
要点:
- 永远不要把用户传来的对象直接
Object.assign到内部对象,尤其是内部对象会被拿去做 IO / 执行 / require 之类的操作时; - 在任何反序列化逻辑中:
- 使用白名单字段;
- 拒绝
__proto__/constructor/prototype; - 采用 schema 校验(zod / yup / joi / class-validator 等)。
3.3.2 不要让客户端直接控制 require / child_process 等路径
很多 RCE 最后都是绕到:
const cp = require('child_process');
cp.exec(userControlledString); // ❌
或:
const vm = require('vm');
vm.runInThisContext(userControlledString); // ❌
经验法则:
- 服务端不要暴露任何“把字符串当代码执行”的能力给客户端;
- 把所有需要 shell 的操作封装成固定命令 + 固定参数,且参数在 server 端做强校验 / 映射。
3.3.3 运行环境加固
即使业务代码被打出了 RCE,也可以降低杀伤力:(wiz.io)
- Node 进程跑在非 root 用户下;
- 容器启用 seccomp / AppArmor 等裁剪系统调用;
- 限制
curl/wget/ 编译器之类工具在镜像里存在; - 限制 Node 进程对宿主机 / 其他容器的网络访问(出网策略);
- 使用 eBPF / 守卫类产品监控
node -> /bin/sh这类可疑进程链路。
4. 建议你现在可以做的具体事情
结合你是前端 / 全栈的背景,我给一个“今天就能动手”的 checklist:
- 资产盘点 & 版本排查
- 找出所有使用 React 19 + RSC / Next.js App Router 的服务(包括内部管理台、B 端项目、低代码平台等)。
- 扫一遍这些项目的
package.json/ lockfile,确认是否落在受影响版本区间。 - 对云环境,可以用 SBOM / 包管理扫描工具(Snyk, Trivy, Wiz 等)自动识别。(wiz.io)
- 立刻升级依赖
- 对每个项目执行 React / Next.js / RSC 插件的升级,优先处理对公网暴露的服务;
- 升级后跑一轮 e2e / 回归(对你 Playwright 体系可以直接复用)。
- 临时 WAF / 入口网关规则
- 对无法当天升级的服务:
- 限制
Next-Action相关请求只允许来自特定网段; - 对可疑 RSC multipart 请求做节流 / 封禁。
- 限制
- 对无法当天升级的服务:
- 日志与入侵迹象检查
- 搜索日志中是否存在异常的 RSC 请求:
- 请求体中包含大量奇怪的 JSON、
__proto__、constructor等; - 带有不正常的 RSC 特征字段名;
- 请求体中包含大量奇怪的 JSON、
- 在服务器上排查:
- 是否出现莫名其妙的
/tmp可执行文件、挖矿进程(xmrig等); - 是否有异常的
node→bash/sh子进程; - 是否有出向可疑 IP 的
curl/wget访问。(securitylabs.datadoghq.com)
- 是否出现莫名其妙的
- 搜索日志中是否存在异常的 RSC 请求:
- 代码层面的长期治理
- 对团队内部 Node / Express / Next 自定义中间件做一次安全 code review:
- 是否有
JSON.parse后直接Object.assign的场景; - 是否有把用户 input 传给
child_process/vm/Function的地方;
- 是否有
- 抽象出一套 “反序列化安全规范” 和 TypeScript 工具(通用
assertNoPrototypeKeys/ schema 校验等)供所有服务端项目使用。
- 对团队内部 Node / Express / Next 自定义中间件做一次安全 code review:
- 安全流程嵌入 CI/CD
- CI 里引入依赖漏洞扫描(npm audit / Snyk / Trivy / Dependabot 等),对高危(Critical)包升级设置“必须处理”策略;
- 对部署前的容器镜像做一次安全扫描,避免把有 CVE 的版本推上生产。(wiz.io)