React2Shell 的故事以及 Next.js 的后续发展

Lobsters Hottest 新闻

摘要

本文详细介绍了 CVE-2025-5518(React2Shell)的发现与披露过程。这是一个存在于 React Server Components 中的严重远程代码执行漏洞,研究人员通过绕过 Flight 协议的验证机制来访问对象原型。

<p><a href="https://lobste.rs/s/lwihzw/react2shell_story_what_happened_next_js">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/05/09 14:40

# React2Shell 的故事以及 Next.js 的后续遭遇 来源: https://sylvie.fyi/posts/react2shell/ 2025 年 12 月 3 日,Meta (https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components) 披露了 CVE-2025-55182 (https://www.cve.org/CVERecord?id=CVE-2025-55182),我们将其命名为 **react2shell**,这是一个存在于 React Server Components (RSC) 中的未认证远程代码执行 (RCE) 漏洞。简而言之,Flight 协议未能正确验证类型,从而允许构造任意代码块并访问对象原型(进而访问原型上的函数)。目前已有几篇关于该漏洞 Flight 代码的优秀技术分析文章,其中最著名的是我的研究搭档 Lachlan Davidson 撰写的 (https://lachlan.nz/blog/the-react2shell-story)。不少朋友问我何时发布自己的技术详解,虽然我觉得现在再写可能有些冗余,但我认为讲述我们是如何偶然发现这一漏洞以及后续发生的故事,依然非常有价值。 在开始之前,我想说明一下,Lachlan 和我处于完全不同的时区,我在 GMT-7,而他在 GMT+13,因此我们的日期和时间会相差 20 小时。 ## 周一 — 传奇开始 ⌗ (https://sylvie.fyi/posts/react2shell/#monday---the-saga-begins) 这一切对我来说始于 11 月下旬。Lachlan 是我在 2019 年通过机器人项目结识的朋友,他注意到了一些奇怪的现象,并在一个我们共同所在的小型群组聊天中提出了疑问。 > *来自 Lachlan 的 Discord @提及,引入了这个问题* 这个问题感觉像是一个有趣的 JavaScript 沙盒逃逸挑战,所以我产生了兴趣,花了一晚上时间寻找任何可能有趣的东西。起初,我们只能访问 Flight 提供的基本类型,包括 `String`、`Number`、`Array`、`Map`、`Date`、`BigInt`、`Object` 和 `Function`。当时我们可用的 gadget 仅允许我们调用一个我们可以通过单个参数引用的函数。有了这个,我们可以轻松执行类似 `Object.constructor.constructor("console.log('meow')")` 的操作来构建任意函数对象,但由于无法访问结果,该函数并未被调用,因而毫无用处。 虽然 Lachlan 最初是在 CMS 平台使用 React 的方式中注意到这种行为的,但很快我们就意识到,React 本身也存在类似的原始操作。 > *Discord 截图,暗示 React Server 可能存在 RCE* 我花了剩下的时间在 Node REPL 中探索这个问题,以便更好地了解我们面对的情况。 > *关于函数原型的 Discord 消息* 此时,我们都没*真的*认为这会取得什么进展。在 Lachlan、我和群组中的其他人之间,有很多评论,大致都是“哈哈,如果我们在 React 中找到了 RCE 那该多疯狂啊”这类的话。这虽然在可能性范围内,但我们认为如果这*真的*存在漏洞,可能早就被人发现了。尽管如此,Lachlan 和我都同意,即使它没有直接漏洞,React 中的这种行为显然也是考虑不周的,所以我们继续寻找。 在接下来的几天里,我和 Lachlan 一直在这个问题上摸索。我们取得了缓慢且渐进的进展,但没有取得任何*实质性的*突破。 ## 周四 — 情况可能变得严肃了 ⌗ (https://sylvie.fyi/posts/react2shell/#thursday---this-is-maybe-getting-serious) 在某个时刻,我们意识到,既然有非零的概率发现某种真正灾难性的问题,我们最好停止在只有我们两个人的地方讨论进一步的工作,于是我们转移到了私信 (DM)。我们都确信一定有*某种*方法可以在这里实现 RCE;只是需要弄清楚具体怎么做。 从这里开始,我决定加入 Lachlan 专门研究 NextJS 的实现,因为在几个广泛使用的基于 React 的框架中,它似乎是最适合用作潜在利用目标的对象。我以默认的 Next 项目为基础进行工作。 > *npm 发现零漏洞* Lachlan 发现并分享给我的一件事是,能够访问某些导入项并将参数绑定到它们。 > *压缩后的 React 代码截图* 然而,问题在于 Webpack 似乎给我们提供的实际可用的导入项非常少,可能没什么用处。我花了几小时浏览默认服务器配置,寻找可能包含有用内容的导入项,但收效甚微。 > *寻找 Node 导入项* 到了凌晨 4:30 左右,我已经在桌子边快要累瘫了,所以我决定休息。 > *我说晚安* 第二天下午我醒来时,除了我原本打算整晚参加的感恩节聚餐外,日程上没有其他安排。正如任何了解我的人都能预料到的那样,我在聚餐的大部分时间里都在手机上阅读 Node 源代码和文档,特别关注 `module` 模块。 > *建议 module 模块中可能存在潜在 gadget* 晚饭后回家,我立即回到桌前继续搜索。 > *从磁盘加载源码* 此时,如果能把 JS 放到磁盘上,执行它简直是小菜一碟。我把精力集中在尝试强制某种特定的文件上传机制上,而 Lachlan 则探索其他途径。那天晚上我大约在凌晨 2:30 睡觉。 大约在我时间的早上 5 点,Lachlan 发消息说他成功了。 > *netcat 监听器收到了 ping* ## 周五 — 恐慌 ⌗ (https://sylvie.fyi/posts/react2shell/#friday---panic) 此时,我已经在积极处理这个问题上花了大约 36 小时,据他估计,Lachlan 已经花了一百多个小时。尽管看起来可能并非如此,但艰难的部分*远*未结束。我们都不傻,我们俩都立刻意识到,我们手里拿着的实际上是一枚核弹。 尽管我们有相当高的信任度,并且作为朋友已经相处了半个多世纪,但我们俩都开始采取防御性的姿态,不确定对方会做什么。我此时还没有完整的 RCE PoC,但我确信我可以很快得到它。我们决定,对我们俩来说最好的做法是冷静下来,睡一觉。 在我们俩恢复理智后,我们决定制定一份签名的书面协议,以缓解对彼此计划的不安。一般条款是:Lachlan 向 Meta 披露该漏洞,我们将彼此共享所有当前或未来的相关 PoC,并且在未经双方同意的情况下,我们都不会向任何人披露有助于其获取 PoC 的任何信息。 此时,我们还回去归档并清除了我们在最初讨论此问题的服务器上的所有消息;本文前面的一些 Discord 截图就是从该档案中重建的。 ## 周六 — 披露 ⌗ (https://sylvie.fyi/posts/react2shell/#saturday---disclosure) 在向 Meta 披露后不久,我们开始为即将到来的极其繁忙的公开披露做准备。如此大规模的漏洞在许多第三方漏洞赏金计划中都是可利用且范围内的,但不用说,我们需要等待 CVE 公开并给人们时间来修补,然后再提交给任何赏金计划。 显然想要利用这个(推测的 :p)千载难逢的机会,我们意识到接下来的几天可以用来完善 PoC,对具有漏洞赏金计划的目标进行被动侦察,并制定实际的提交计划。 我还觉得提前在 Twitter (https://twitter.com/_sy1vi3/status/1994948792295330278) 上发布我们的 PoC 哈希值会很有趣: > *在 Twitter 上发布的 PoC 哈希* 由于 Lachlan 大部分时间都在与 Meta 和 Vercel 进行讨论,我主导了侦察工作,主要目标有两个:(1) 尽可能多地识别相关且有趣的漏洞赏金计划,以及 (2) 大致了解互联网上有多少存在漏洞。我并行开始了这两项任务。 我开始构建一个后端来管理已知、已扫描和易受攻击的域名。我启动了一个简单的 MariaDB 实例来跟踪事情,并用 Golang 编写了与之交互的代码。Lachlan 和我都很熟悉 Go,我们同意这至少是我们所需任务的一个可接受选择。 我的脚本的第一版非常基础,但它在我设计更完整的方案时完成了所需的工作。我找到了一个包含 400 万个域名的列表,并简单地遍历了所有这些域名,在 HTTP 响应体中寻找 NextJS 签名。 在它运行时,我着手索引 HackerOne 计划,并进行初步的人工筛选,以确定它们是否值得进一步关注。我主要关注的是计划对关键漏洞支付的策略、第三方库漏洞纳入范围的天数 (n-days)、共享根本原因(即多个范围内的服务是否易受攻击)以及域名是否在范围内/外。 这需要筛选大量信息,而且 HackerOne 并不一定让你容易在一处看到所有信息。最终,我发现最简单的解决方案是将整个计划描述复制/粘贴到 LLM 中,并要求它给我提供我喜欢的结构化数据。我对 HackerOne 上的大约 30 个计划做了这个,然后在 BugCrowd 上又对十几个看似有趣的计划做了同样的事情。 ## 周日 — 侦察 ⌗ (https://sylvie.fyi/posts/react2shell/#sunday---recon) 完成之后,我对前 400 万个域名的初步扫描似乎表明,其中约 2% 运行着某种形式的 NextJS。遗憾的是,只有主要版本 15 及以后使用 app router 的版本存在漏洞,所以这个数字有点误导。这一次,懒惰和不更新实际上确实让一些人免于易受攻击。 无论如何,这非常令人鼓舞——正如我们预期的那样,似乎公共互联网的很大一部分正在使用易受攻击的 Next。 另一方面,Hetzner 对我的工作并不太满意。 > *Hetzner 滥用邮件* 我随后向 Hetzner 道歉,然后改用我家乡的 IP 而不是他们珍贵的数据中心继续我的“滥用” 👍。 此时,我也着手修改我的扫描逻辑,使其稍微更健壮。我没有使用默认的 Golang HTTP 客户端,而是使用了无头浏览器,这绕过了一些基本的反机器人措施,这似乎由于大量为 LLM 或由 LLM 进行的抓取而变得极其常见。 以前我在 HTML 源码中查找是否存在 `_next`,而我的新策略是等待网站完全加载,然后在控制台中评估 `next.version`。这种新策略检查一个网站需要更长的时间,但它更健壮,并且还会告诉我们是易受攻击的版本之一,还是应该忽略的版本。 有了扫描网站的工作系统和范围内/外域名列表,接下来要做的是枚举通配符子域名。我尝试了许多服务,坦率地说,它们在某种程度上都 suck,但其中 suck 得最少的是 Profundis (https://profundis.io/),我购买了最小级别的 API 信用额度。 我编写了一些逻辑,获取范围内域名和通配符列表,查询子域名,然后与范围外列表进行过滤并去重,从而给我一个可以输入到我的扫描器中的站点列表。 ## 周二 — 准备 ⌗ (https://sylvie.fyi/posts/react2shell/#tuesday---preparation) 在花费了一两天时间优化和调整我的扫描器后,我们从 Meta 那里得知公告将在太平洋时间第二天早上 7 点发出。我此时决定,最合适的做法是在我的目标数据库上加上一个简单的前端。 显然,这里唯一正确的做法是要求 Claude 用 NextJS 构建一个管理界面。这是一个非常简单的任务,它在几分钟内就构建出了所需的东西。 > *管理器仪表板主页* 有趣的是,在没有具体指导的情况下,Claude 选择的 NextJS 版本是 14.2.15,这个版本太旧,不存在漏洞。 管理仪表板支持添加、删除和编辑有关各种赏金计划的信息,以及输入域名范围详情,并为发现的站点分派被动子域名扫描和主动漏洞扫描。 > *显示 Twitter 站点易受攻击的管理器仪表板* 它并没有添加我之前几天在后端已经创建的任何功能,但拥有 UI 使得与数据交互变得不那么痛苦。 ## 周三 — 零日 ⌗ (https://sylvie.fyi/posts/react2shell/#wednesday---zero-day) 终于,在超过一周几乎没怎么睡觉之后,Meta 发布了关于 CVE-2025-55182 的公告。我趁机揭示了我前几天在 Twitter 上发布的第一个哈希值。 > *and here we go gif* 在那点乐子之后,Lachlan 和我开始尝试在我们识别的目标上运行我们的漏洞利用。我们立即拿下了几个,但我们很快意识到,像 Cloudflare 这样的 WAF 提供商正在识别并阻止我们的 PoC。我很快设法构建了一个修改版的 PoC,通过将 payload 放在 `eval(atob())` 内部,规避了一些站点使用的一些天真的自定义(?)规则。 令人沮丧的是,Cloudflare 的正则表达式似乎(起初)相当健壮,阻止了访问函数构造函数所需的 `:constructor"` 语法。虽然令人极其沮丧,但我们确实预料到这会是个问题,鉴于 Meta 和 Vercel 在公开披露之前与 Cloudflare 和其他 WAF 供应商进行了出色的合作,正是出于这个原因。 顺便说一句,似乎运行其他易受攻击版本 NextJS 的 Cloudflare Workers 平台实际上并不可利用,因为 JS 运行时首先就缺乏 `Function`。我们*被告知* Vercel 托管的站点有类似的东西保护它们,但后来证明并非如此。 在迅速耗尽非 Cloudflare/Vercel 站点后,Lachlan 和我花了下午剩下的时间构建一个不会触发 WAF 的 payload。我们最初的努力主要集中在寻找访问 `Function` 构造函数的替代方法上,我们花了大约 10 个小时工作,但没有太大成功。 在所有这些混乱中,有人在 Twitter 上发布了一个假的 PoC,并获得了大量关注。我正要开车回家吃午饭时,看到了关于它的通知,立即慌了。 一旦我回到家并实际阅读了被吹捧为 PoC 的代码,我觉得非常有趣,但它确实最终变成了某种麻烦。几个有实际声誉的网站开始将其报告为真的(大概没有检查),并且出现了错误地声称站点安全的扫描工具。 > *关于假 PoC 的 Discord 消息* 由于 Lachlan 分心处理假 PoC 的后果,而我自己对无法绕过 Cloudflare WAF 感到沮丧,我退后一步,考虑其他选择。我们一直将其视为 JavaScript 沙盒逃逸问题,我想也许从更传统的 WAF 绕过视角来看可能会有好处。 我记得今年早些时候我和 Lachlan 以及其他人有过一次讨论,关于默认情况下,许多 WAF 会在达到一定 body 大小后停止扫描。 > *WAF 最大 body 大小讨论* 值得一提的是,为了防止 DoS 攻击,NextJS 限制请求

相似文章

React2Shell 漏洞事件

Hacker News Top

安全研究员 Lachlan 于 2025 年 11 月 30 日发现并报告了一个名为“React2Shell”的严重远程代码执行漏洞,该漏洞存在于 React 服务器组件协议中,并向 Meta 进行了报告。Meta 于 12 月 3 日发布了修复程序和安全公告(CVE-2025-55182),敦促开发者立即更新,因为该漏洞影响了数百万使用 React/Next.js 构建的网站。

真的有人喜欢 React 吗?

Hacker News Top

关于 React 的批评性博客文章合集,涵盖性能问题、一个严重安全漏洞(CVE-2025-55182,CVSS 10.0)以及更广泛的生态系统问题。

CVE-2026-48710:维护者的视角

Lobsters Hottest

Marcelo Trylesinski 分享了他对 CVE-2026-48710 的看法,这是一个 Starlette 中的安全漏洞,涉及通过操纵 Host 标头绕过基于路径的授权。他认为该漏洞源于应用模式和部署方式,而非框架本身。