High Fidelity Detection Mechanism for RSC/Next.js RCE (CVE-2025-55182 & CVE-2025-66478)
本文转自 Adam Kues 并作补充
今天早上,Next.js 发布了安全公告存在一个漏洞,即使在默认配置下也会导致远程代码执行 (RCE),且无需任何前提条件。此问题的根源在于 Next.js 使用的 React 服务器组件。
在过去的一天里,我们注意到 GitHub 上流传着大量不正确的 PoC,这些 PoC 并不能以较高的置信度真实地证实此漏洞的存在。
我们在 GitHub 上看到的一些 PoC 完全误诊了漏洞的根本原因,关键在于它无需任何前提条件(例如上下文中存在某些函数)即可在 Next.js 上利用该漏洞。
虽然我们已在下方提供了 HTTP 请求和预期响应,但如果您希望使用工具来检测主机列表中的此问题,请参阅我们 GitHub 上的漏洞代码库:https://github.com/assetnote/react2shell-scanner。
该漏洞的原始作者确认,GitHub 上的这些 PoC 与之前分享给 React 和 Next.js 维护者的漏洞利用程序并不相同。请参阅作者在 GitHub 上的留言。https://react2shell.com/。
虽然有几种机制可以确定资产是否正在运行 React Server Components (RSC),但仅仅验证 RSC 是否存在并不足以确定资产是否真的容易受到此 RCE 攻击。
因此,我们的安全研究团队进行了调查,以确定可用于安全可靠地确认 Next.js 应用程序中是否存在此 RCE 漏洞的 HTTP 请求。
注意:我们收到报告称,一些用户声称在响应摘要错误的主机上存在漏洞,但并未明确发送以下有效载荷。此有效载荷对于确认该漏洞的存在至关重要。即使以下有效载荷可能在受影响的 Next.js 版本范围之外也能检测到漏洞;但是,由于 WAF 会对完整的 RCE 有效载荷进行保护,因此仍然需要进行检查。
可以使用以下HTTP请求来确认是否存在漏洞:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 POST /
Host: hostname
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
Next-Router-State-Tree: %5B%22%22%2C%7B%22children%22%3A%5B%22__PAGE__%22%2C%7B%7D%2Cnull%2Cnull%5D%7D%2Cnull%2Cnull%2Ctrue%5D
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
X-Nextjs-Html-Request-Id: SSTMXm7OJ_g0Ncx6jpQt9
Content-Length: 232
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
{}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"
["$1:a:a"]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--当使用存在漏洞的 Next.js 版本发送上述请求时,HTTP 响应将如下所示:
1
2
3
4
5
6
7
8
9
10 500 Internal Server Error
Date: Thu, 04 Dec 2025 06:16:39 GMT
Content-Type: text/x-component
Connection: keep-alive
Cache-Control: no-store, must-revalidate, no-cache, max-age=0
Vary: rsc
Content-Length: 76
0:{"a":"$@1","f":"","b":"yd-J8UfWl70zwtaAy83s7"}
1:E{"digest":"2971658870"}检查是否存在
E{"digest"500 状态码,可以可靠地返回环境中存在漏洞的主机。
为什么这种检查有效?
之所以能够通过此检查区分易受攻击的主机和非易受攻击的主机,是因为 React-Server 依赖项使用冒号来分隔对象属性。请参见以下代码片段:
1
2
3
4
5
6
7
8
9
10
11
12 function getOutlinedModel<T>(
response: Response,
reference: string,
parentObject: Object,
key: string,
map: (response: Response, model: any, parentObject: Object, key: string) => T,
): T {
const path = reference.split(':');
// ... snip ...
for (let i = 1; i < path.length; i++) {
value = value[path[i]];
}例如,如果我们像这样在多部分请求中传递 JSON:
1
2
3
4
5
6
7
8
9 ------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"
["$1:a:b"]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
{"a":{"b":"foo"}}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--这将转化为以下内容:
1 "$1:a:b" -> {"a":{"b":"foo"}}.a.b -> "foo"在存在漏洞的 React Server 版本中,我们可以通过以下 multipart 请求强制返回 500 错误:
1
2
3
4
5
6
7
8
9 ------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"
["$1:a:a"]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"
{}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--之所以会引发异常,是因为它最终映射到以下内容:
1 "$1:a:a" -> {}.a.a -> (undefined).a -> 500React Server Components 的补丁版本为这种冒号表示法添加了额外的检查,从而防止崩溃发生:
1
2
3
4 const name = path[i];
if (typeof value === 'object' && hasOwnProperty.call(value, name)) {
value = value[name];
}这意味着如果
:语法引用了不存在的属性,则会被忽略。这意味着补丁之后,我们将不再收到 500 错误。Assetnote 攻击面管理平台的客户今天早些时候通过我们安全研究团队开发的高保真检查收到了有关其整个攻击面存在此漏洞的通知。