Next.js 在 App Router 中引入的 Server Actions 功能极大地简化了前后端交互流程,但其底层使用的通信协议------React Flight 协议,曾被发现存在严重的安全漏洞。该漏洞允许攻击者通过精心构造的数据包,绕过安全检查,最终在服务器端实现远程代码执行 (RCE) 。
一、 Server Actions 与 Flight 协议机制
为了理解漏洞,首先需要了解 Server Actions 的运行机制。
Server Actions 的设计初衷
在 Next.js 13+ 中,Server Actions 允许开发者定义服务端函数,并直接在客户端组件中调用,无需显式创建 API 路由。
js
// app/actions.js
'use server'
export async function submitForm(formData) {
// 该函数在服务端运行
await db.users.create({ name: formData.get('name') })
return { success: true }
}
// app/page.jsx (客户端组件)
import { submitForm } from './actions'
export default function Page() {
return (
<form action={submitForm}>
<input name="name" />
<button type="submit">提交</button>
</form>
)
}
底层通信:Flight 协议
当用户触发上述表单时,浏览器并非发送普通的 JSON 请求,而是通过 React Flight 协议进行通信。流程如下:
- 序列化:React 将客户端数据序列化为 Flight 协议格式。
- 传输 :通过 HTTP POST 请求发送,请求头包含
Next-Action标识符。 - 反序列化:Next.js 服务端接收请求,解析 Flight 格式数据,还原参数。
- 执行与响应:执行服务端函数,并将结果再次通过 Flight 协议编码返回给客户端。
Flight 协议的数据结构:
Flight 协议将数据拆分为多个 Chunk (块)。Chunk 之间可以通过 ID 互相引用,特殊数据类型使用 <math xmlns="http://www.w3.org/1998/Math/MathML"> 前缀表示(如 前缀表示(如 </math>前缀表示(如D 代表 Date,$K 代表 FormData)。
漏洞的核心,正是出现在服务端解析和反序列化这些 Chunk 的过程中。
二、 漏洞成因:攻击链的三环
该 RCE 漏洞的利用过程并非单一缺陷导致,而是串联了三个逻辑漏洞。
第一环:引用解析中的路径遍历
Flight 协议允许使用冒号 : 对引用对象的属性进行嵌套访问。例如,$0:users:0:name 表示访问 Chunk 0 中 users 数组第 0 项的 name 属性。
服务端解析逻辑的简化伪代码如下:
js
// 服务端解析逻辑示意
let value = chunk.value;
const path = reference.split(':'); // 分割路径
for (let i = 1; i < path.length; i++) {
value = value[path[i]]; // 逐层访问属性
}
漏洞点:服务端并未对路径中的属性名进行过滤或白名单验证。
这意味着攻击者可以构造特殊路径来访问 JavaScript 对象的原型链属性:
$0:__proto__:then:访问原型链上的then方法。$0:constructor:constructor:获取对象的构造函数的构造函数,即Function构造函数。
第二环:伪造 Chunk 注入
在 React 内部实现中,Chunk 被视为一种类似 Promise 的对象。服务端在解析时,会检查对象的 status 属性。如果 status 标记为完成状态(如 "resolved_model"),服务端会直接解析其内容,而不会验证该对象是否由系统生成。
攻击者可以构造一个伪造的 Chunk 对象作为 Payload 发送给服务器:
json
{
"status": "resolved_model", // 伪装成已解析状态
"value": "...",
"_response": { // 注入恶意的内部 Response 对象
"_prefix": "恶意代码字符串",
"_formData": { "get": "待替换的函数占位符" }
}
}
由于缺乏严格的类型验证,服务端会接纳这个伪造的对象及其包含的恶意内部属性 _response。
第三环:Function 构造函数注入与代码执行
攻击者的最终目标是利用上述两个漏洞,将代码字符串转换为可执行函数。
- 替换关键方法 :攻击者利用第一环的"路径遍历",将伪造 Chunk 中
_response._formData.get的值指向$1:constructor:constructor(即Function构造函数)。 - 触发执行 :攻击者在 Payload 中包含一个
$B(Blob) 类型的引用。
当服务端解析 $B 类型引用时,会执行如下逻辑:
js
// React 源码逻辑简化
case 'B': {
const prefix = response._prefix; // 攻击者注入的代码字符串
const blobKey = prefix + id;
// 关键点:调用 _formData.get
// 此时 get 已被替换为 Function 构造函数
const backingEntry = response._formData.get(blobKey);
return backingEntry;
}
上述代码实际上等效于执行了:
js
new Function("恶意代码字符串...")
这会创建一个匿名函数,其函数体即为攻击者注入的代码。随后,该函数被作为 Promise 的回调执行,从而在服务器端完成了远程代码执行 (RCE) 。
三、 攻击流程总结
整个攻击过程可以概括为以下步骤:
- 构造 Payload:攻击者发送包含伪造 Chunk 的恶意数据包。
- 原型链污染 :利用路径遍历漏洞(
__proto__),劫持 Promise 解析过程。 - 构造函数替换 :利用路径遍历获取
Function构造函数,替换掉内部的_formData.get方法。 - 伪造对象注入 :服务器反序列化伪造的 Chunk,加载恶意的
_response对象。 - 代码编译与执行 :服务器处理 Blob 引用时,错误地调用
Function构造函数,将恶意字符串编译为函数并在服务端执行。
四、 总结与防御
防御措施: react.dev/blog/2025/1... 打开这篇文章复制进 cursor 诊断项目的package.json 生成依赖升级命令
评论: 这是 FE 发展至今对全球互联网影响最大的 Bug,无数一键部署到 Vercel ,Netlify, Cloudflare 的 NextJS 网站都是直接暴露的可攻击端点。
所幸 NextJS 这一套纯 FaaS 方案其实私有化部署到自己服务器并不方便,所以互联网上大部分都是部署到 Vercel 自己服务或免费 FaaS平台的,也是自我得之自我失之了。
坏处就是免费的 FaaS 服务被影响最大,比如 CloudFlare 在处理本问题时 2025/12/5 又出现了全球宕机。虽然我是 CF 粉,但这个洗不了,股价还有下跌空间。