CVE-2026-23870:单个 POST 请求可冻结 Next.js 服务器
CVE-2026-23870: A Single Post Freezes Any Next.js Server
作者披露 React Server Components 表单解析中的拒绝服务漏洞 CVE-2026-23870:每个 $K 嵌套表单指针都会完整遍历一次请求字段列表,发送 10000 个指针加 10000 个字段约 900 KB 的请求即可触发上亿次字符串检查,阻塞 Node.js 单线程。
作者以第一手复现记录拆解 React Server Components 表单解析的复杂度问题,并给出受影响版本与修复方式。
我是如何发现的
现代 React 允许你把表单直接连接到一个在服务器上运行的函数,即“服务器操作(server action)”。用户提交表单,函数在后端运行。在它运行之前,React 必须接收原始的 HTTP 请求,并从中重建你的表单数据。这段重建代码在每一次服务器操作调用时都会运行,而且它完全基于发送者控制的数据工作。正是那种值得一读的代码。
React 的格式允许表单中的一个字段指向另一个字段。其中一个指针,React 将其写作 $K,意思是“这里有一个嵌套表单”。下面这段代码负责解析单个 $K:
case 'K': {
const data = new FormData();
const keys = Array.from(response._formData.keys()); // list every field in the request
for (let i = 0; i < keys.length; i++) { // then check all of them
if (keys[i].startsWith(formPrefix)) { /* ... */ }
}
return data;
}通俗地说:对于每个 $K 指针,React 都会遍历请求中的整个字段列表来寻找匹配项。所以一个指针就要对每个字段做一次完整遍历。
问题就在这里。没有任何东西限制一个请求可以包含多少个 $K 指针,也没有任何东西限制它可以包含多少个字段。发送者两者都能决定。在一个请求中放入 10,000 个指针和 10,000 个字段,React 就会对 10,000 个字段做 10,000 次遍历。那就是 1 亿次字符串检查,而且它会一口气全部跑完,中间从不停顿。
“从不停顿”这一点正是它变成服务器杀手的原因。Node.js 在单个线程上处理每一个传入的请求。当它艰难地跑完那 1 亿次检查时,它无法做任何其他事情。其他所有访客都只能等它跑完。
那些帮不上忙的限制
React 确实在这段代码旁边有两个大小限制,但两个都管不到它。第一个只限制数组可以嵌套多深。第二个限制一次操作调用可以接受多少个参数,这听起来应该能拦住我,但你可以把那个巨大的指针列表埋到比限制愿意查看的层级更深一层的地方来绕过它:
level 1 (counted): ["$2"]
level 2 (not counted): ["$Knomatch0", … ,"$Knomatch9999"]限制只看到顶层的那一项,从未注意到下面藏着的 10,000 项。
无需登录
重建发生在任何安全检查运行之前。在损害造成之前,没有任何登录、CSRF 令牌或来源检查会查看这个请求。你唯一需要的是应用的“操作 ID”,一个命名服务器函数的哈希值,而应用基本上会把它交出来:它印在页面的 HTML 里,也以纯文本形式存在于浏览器反正都会下载的 JavaScript 文件中。拿到它,发一个 POST 请求就行。如果该操作位于公开页面上,你甚至不需要账户。如果它在登录之后,任何普通账户都可以,因为每个已登录用户都走同一条路径。
用 10,000 个指针和 10,000 个填充字段,那个请求大约 900 KB。
验证
我从目标中提取了一个操作 ID,构造了上面的请求,发送出去,并计时它花了多久,同时另一个循环不断 ping 另一个页面,看服务器是否还在响应。
在我笔记本电脑上的普通生产构建中,一个请求就让服务器冻结了大约 4 秒。该操作最终确实会返回,但只有在整个扫描完成后才会返回,而在这个时间窗口内到达的任何东西要么排队等待,要么超时。连续五个请求造成了大约 20 秒的完全停机。
一个真实生产环境的配置更糟:每个请求大约 10 秒,连续三个就足以让前面的负载均衡器判定服务器已死,将其从轮换中移除,并开始向与我毫无关系的人返回 503 错误。
谁受影响
这段代码位于 React 在不同配置中共享的一块代码里,因此并不特定于某个框架。React 在 19.0.6、19.1.7 和 19.2.6 中修复了它。任何低于这些版本的 19.0、19.1 或 19.2 版本都存在漏洞,涉及所有三个已发布的包:
react-server-dom-webpackreact-server-dom-turbopackreact-server-dom-parcel
(react-server-dom-esm 构建运行的是完全相同的代码,但未在公告中提及。)
实际上,这基本上就是任何 Next.js 14+ 应用,或任何其他 React Server Components 配置,运行在未打补丁的 React 19.0 到 19.2 版本上,并且在其可被访问到的任何位置至少有一个 server action。一个仅仅记录表单字段的 action 就足够了。
修复
整个问题在于 React 针对每个指针都会重新扫描一次完整的字段列表。修复后,它改为对整个请求只扫描一次列表。
React 在传入的表单数据周围添加了一个小包装器,它会记录处理到的位置,并在每个字段被使用后将其删除。因此,现在所有指针共享一次从头到尾的字段遍历,而不是每个指针都从头重新开始。这样一来,10,000 个指针不再意味着 10,000 次扫描,我的 payload 也不再有任何特殊之处。它随 React 的公告 GHSA-rv78-f8rc-xrxh 一同发布。
Meta 将该补丁作为 CVE-2026-23870 上游发布。我是通过观察提交落地才得知的,并非来自任何状态更新。在我写这篇文章时,该报告在漏洞赏金平台上仍处于“New”状态,我在公告中没有任何署名,除了自动确认之外从未收到过任何回复。所以:悄悄修复,然后被无视。我两次跟进,并在此期间一直推迟发布此文。没有任何回音,所以就有了这篇文章。
时间线
- Apr 21, 2026
报告给 Meta
- May 6, 2026
作为 CVE-2026-23870 上游打补丁
- Oct 8, 2026
公开文章
来源:Hacker News · simonkoeck.com