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-webpack
    • react-server-dom-parcel
    • react-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)

  1. 攻击者发现目标使用 RSC / Next.js App Router
    • 扫描响应头 / 路由结构(如存在 Next-Action 头、/_rsc//_actions 等特征)。
  2. 构造一个特殊的 Flight JSON / multipart 请求
    • 在某个模型 / chunk 中注入类似:
      • 带有 __proto__constructor 等特殊键
      • 带有 then 字段,让对象被当成 Promise / thenable
      • 带有 _response_prefix 等会在内部被拼接执行的字段
  3. React 服务端在 decode 时:
    • 把客户端发来的对象 merge 进内部记录对象;
    • 没有过滤 __proto__ / constructor 等危险键;
  4. 由于原型被污染 / 字段被注入:
    • 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 后续行为包括:

  • 读取 .envnext.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)

  1. 禁用 / 限制 RSC / Server Actions:
    • Next.js 中可以临时关闭 App Router 的 RSC 功能(如迁回 Pages Router,或移除 Server Actions)。
  2. WAF / 反向代理层做过滤:
    • 阻断带有 Next-Action 头的请求,或仅在内部受信网段允许此类请求。
    • 拦截可疑字段(比如 RSC 标志性的 multipart 字段名)——这个需要配合安全团队谨慎定制规则。
  3. 最小暴露面:
    • 不要让内部管理 / 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:

  1. 资产盘点 & 版本排查
    • 找出所有使用 React 19 + RSC / Next.js App Router 的服务(包括内部管理台、B 端项目、低代码平台等)。
    • 扫一遍这些项目的 package.json / lockfile,确认是否落在受影响版本区间。
    • 对云环境,可以用 SBOM / 包管理扫描工具(Snyk, Trivy, Wiz 等)自动识别。(wiz.io)
  2. 立刻升级依赖
    • 对每个项目执行 React / Next.js / RSC 插件的升级,优先处理对公网暴露的服务;
    • 升级后跑一轮 e2e / 回归(对你 Playwright 体系可以直接复用)。
  3. 临时 WAF / 入口网关规则
    • 对无法当天升级的服务:
      • 限制 Next-Action 相关请求只允许来自特定网段;
      • 对可疑 RSC multipart 请求做节流 / 封禁。
  4. 日志与入侵迹象检查
    • 搜索日志中是否存在异常的 RSC 请求:
      • 请求体中包含大量奇怪的 JSON、__proto__constructor 等;
      • 带有不正常的 RSC 特征字段名;
    • 在服务器上排查:
      • 是否出现莫名其妙的 /tmp 可执行文件、挖矿进程(xmrig 等);
      • 是否有异常的 nodebash/sh 子进程;
      • 是否有出向可疑 IP 的 curl/wget 访问。(securitylabs.datadoghq.com)
  5. 代码层面的长期治理
    • 对团队内部 Node / Express / Next 自定义中间件做一次安全 code review:
      • 是否有 JSON.parse 后直接 Object.assign 的场景;
      • 是否有把用户 input 传给 child_process / vm / Function 的地方;
    • 抽象出一套 “反序列化安全规范” 和 TypeScript 工具(通用 assertNoPrototypeKeys / schema 校验等)供所有服务端项目使用。
  6. 安全流程嵌入 CI/CD
    • CI 里引入依赖漏洞扫描(npm audit / Snyk / Trivy / Dependabot 等),对高危(Critical)包升级设置“必须处理”策略;
    • 对部署前的容器镜像做一次安全扫描,避免把有 CVE 的版本推上生产。(wiz.io)