React 史诗级漏洞: SSR Server Action 协议导致服务器远程代码执行

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 协议进行通信。流程如下:

  1. 序列化:React 将客户端数据序列化为 Flight 协议格式。
  2. 传输 :通过 HTTP POST 请求发送,请求头包含 Next-Action 标识符。
  3. 反序列化:Next.js 服务端接收请求,解析 Flight 格式数据,还原参数。
  4. 执行与响应:执行服务端函数,并将结果再次通过 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 构造函数注入与代码执行

攻击者的最终目标是利用上述两个漏洞,将代码字符串转换为可执行函数。

  1. 替换关键方法 :攻击者利用第一环的"路径遍历",将伪造 Chunk 中 _response._formData.get 的值指向 $1:constructor:constructor(即 Function 构造函数)。
  2. 触发执行 :攻击者在 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)

三、 攻击流程总结

整个攻击过程可以概括为以下步骤:

  1. 构造 Payload:攻击者发送包含伪造 Chunk 的恶意数据包。
  2. 原型链污染 :利用路径遍历漏洞(__proto__),劫持 Promise 解析过程。
  3. 构造函数替换 :利用路径遍历获取 Function 构造函数,替换掉内部的 _formData.get 方法。
  4. 伪造对象注入 :服务器反序列化伪造的 Chunk,加载恶意的 _response 对象。
  5. 代码编译与执行 :服务器处理 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 粉,但这个洗不了,股价还有下跌空间。

相关推荐
Dragon Wu1 小时前
ReactNative Expo 使用总结(基础)
javascript·react native·react.js
shaohaoyongchuang2 小时前
01-分布式基础-创建微服务项目
分布式·微服务·架构
海市公约2 小时前
Python操作SQLite数据库:从基础语法到完整项目实战
数据库·ide·python·程序人生·架构·pycharm·sqlite
Mintopia2 小时前
🧠 AI驱动的B端服务架构猜想
人工智能·安全·架构
云小逸2762 小时前
openEuler 多算力虚拟化性能实测
架构
Mintopia2 小时前
🤖 AIGC技术服务商与Web平台的API对接标准与技术规范
人工智能·架构·aigc
鱼鱼块2 小时前
揭开浏览器底层的秘密:为什么一个 Chrome 能跑这么快又这么稳?
架构·操作系统·浏览器
qq_229058012 小时前
react的3中请求
前端·react.js·前端框架
sugar椰子皮2 小时前
【爬虫框架-2】funspider架构
爬虫·python·架构