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 粉,但这个洗不了,股价还有下跌空间。

相关推荐
yunteng5218 分钟前
通用架构(同城双活)(单点接入)
架构·同城双活·单点接入
麦聪聊数据43 分钟前
Web 原生架构如何重塑企业级数据库协作流?
数据库·sql·低代码·架构
程序员侠客行1 小时前
Mybatis连接池实现及池化模式
java·后端·架构·mybatis
bobuddy3 小时前
射频收发机架构简介
架构·射频工程
桌面运维家3 小时前
vDisk考试环境IO性能怎么优化?VOI架构实战指南
架构
lbb 小魔仙4 小时前
【HarmonyOS实战】React Native 表单实战:自定义 useReactHookForm 高性能验证
javascript·react native·react.js
一个骇客5 小时前
让你的数据成为“操作日志”和“模型饲料”:事件溯源、CQRS与DataFrame漫谈
架构
鹏北海-RemHusband5 小时前
从零到一:基于 micro-app 的企业级微前端模板完整实现指南
前端·微服务·架构
2的n次方_8 小时前
Runtime 内存管理深化:推理批处理下的内存复用与生命周期精细控制
c语言·网络·架构
前端市界9 小时前
用 React 手搓一个 3D 翻页书籍组件,呼吸海浪式翻页,交互体验带感!
前端·架构·github