# Next.js 与中间件漏洞:失效的授权机制 **Published by:** [LXDAO](https://paragraph.com/@lxdao/) **Published on:** 2025-04-07 **URL:** https://paragraph.com/@lxdao/next-js ## Content 原作:Rachid.A 原文: https://zhero-web-sec.github.io/research-and-things/nextjs-and-the-corrupt-middleware 本期文章由 LXDAO 成员 Yewlne 翻译。 译文正文 最近,我与化名为 inzo_ 的 Yasser Allam 决定联手开展研究。在讨论了多个潜在目标后,我们决定将焦点放在 Next.js https://github.com/vercel/next.js GitHub 上拥有 13 万 Stars,目前每周下载量超过 940 万次)。这是一个我非常熟悉的框架,与它有美好的创作经历,正如我之前的研究成果 https://zhero-web-sec.github.io/research-and-things/ 所证明的那样。因此,本文中的"我们"自然指代我们两人。 Next.js 是一个基于 React 的全功能 JavaScript 框架,拥有丰富的特性 — — 是深入研究细节的理想场所。怀着信念、好奇与韧性,我们踏上旅程,探索那些鲜为人知的角落,寻找藏匿其中的宝藏。 不久之后,我们就在中间件中发现了一个重大问题。其影响范围广泛,所有版本均受影响,且利用此漏洞无需任何前置条件 — — 我们将很快进行详细展示。目录Next.js 中间件授权神器:宝藏级老代码执行顺序与 middlewareInfo.name授权神器:昨日已成诗,今朝更值得/src 目录最大递归深度漏洞利用绕过授权/重写绕过 CSP通过缓存投毒实现 DoS(What?)澄清安全公告 — CVE-2025–29927免责声明结语Next.js 中间件中间件允许你在请求完成之前执行代码。然后,你可以根据传入的请求,通过重写、重定向、修改请求或响应头,或直接返回响应的方式来修改响应内容(摘自 Next.js 文档)。 作为一个完整的框架,Next.js 拥有自己的中间件(middleware) — — 这是一个重要且被广泛使用的特性。它的应用场景众多,其中最重要的包括:路径重写(Path rewriting)服务器端重定向(Server-side redirects)向响应添加头信息(如 CSP 等)元素最重要的是:身份验证(Authentication)和授权(Authorization)中间件的一个常见用途是进行授权,这涉及基于特定条件来保护特定路径。 身份验证和授权:在授予对特定页面或 API 路由的访问权限之前,确保用户身份并检查会话 Cookie(Next.js 文档)。 示例:当用户尝试访问 /dashboard/admin 时,请求首先会通过中间件,中间件会检查用户的会话 cookie 是否有效以及是否具有必要的权限。如果验证通过,中间件便转发请求;否则,中间件会将用户重定向到登录页面:授权神器:宝藏级老代码正如一位伟人曾经说过的,”talk is cheap, show me the bug”,让我们避免过多的叙述,直接切入主题;我们在浏览框架的旧版本(v12.0.7)时,我们发现了这段代码 https://github.com/vercel/next.js/blob/v12.0.7/packages/next/server/next-server.ts 当 Next.js 应用程序使用中间件时,会调用 runMiddleware 函数。除了其主要功能外,该函数还会获取 x-middleware-subrequest 头部的值,并用它来判断是否应该应用中间件。该头部值会使用冒号(:)作为分隔符被拆分成列表,然后检查这个列表是否包含 middlewareInfo.name 值。这意味着,如果我们在请求中添加带有正确值的 x-middleware-subrequest 头部,那么中间件 — — 无论其用途如何 — — 将被完全忽略,请求将通过 NextResponse.next() 被转发,并且将完成到原始目的地的路径,而不受中间件的任何影响。这个头部及其值就像一把”万能钥匙”,可以绕过所有规则。此时我们已经意识到发现了一个惊人的问题,接下来需要完成最后几个拼图。 要让我们的”万能钥匙”生效,它的值必须包含 middlewareInfo.name,但这个值究竟是什么呢? 执行顺序与 middlewareInfo.name middlewareInfo.name 的值非常容易推测,它仅仅是中间件所在的路径。要了解这一点,我们需要简单了解一下中间件在旧版本中的配置方式。 首先,在 12.2 版本之前 — — 这个版本中中间件约定发生了变化 — — 文件必须命名为 _middleware.ts。此外,app 路由器(router)仅在 Next.js 的版本 13 中才引入。当时存在的唯一路由器是 pages 路由器,因此该文件必须放在 pages 文件夹内(特定于路由器)。 有了这些信息,我们就能推断出中间件的确切路径,从而猜测 x-middleware-subrequest 头部的值。这个值只是由目录名称(即当时存在的唯一路由器名称)和文件名组成,遵循当时以下划线开头的命名约定: x-middleware-subrequest: pages/_middleware 当我们尝试绕过那些被配置为系统性地将访问尝试从 /dashboard/team/admin 重定向到 /dashboard 的中间件时:成功了,我们侵入了 ⚔️ 我们现在可以完全绕过中间件,从而绕过任何基于它的保护系统,最典型的就是授权,就像我们上面的例子。这个发现相当惊人,但还有其他需要考虑的点。 12.2 之前的版本允许嵌套路由在目录树的任何位置(从pages文件夹开始)放置一个或多个_middleware文件,并且它们有执行顺序,正如我们在从Web Archive中检索到的旧文档截图中所看到的:这对我们的漏洞利用意味着什么? 可能性 = 路径中的层级数量 因此,要访问/dashboard/panel/admin(受中间件保护),middlewareInfo.name的值有三种可能性,相应地x-middleware-subrequest的值也有三种可能性: pages/_middleware 或 pages/dashboard/_middleware 或 pages/dashboard/panel/_middleware授权神器:昨日已成诗,今朝更值得到目前为止,我们认为只有版本 13 之前的版本容易受到攻击,因为中间件已在源代码中被移动,并且我们还没有覆盖它的所有方面。我们推测维护者一定已经注意到了这个漏洞,并在版本 13 的重大更改之前修复了它,所以我们向框架维护者报告了这个漏洞并继续了我们的研究。 令我们大为惊讶的是,在最初发现后两天,我们发现所有版本的 Next.js — — 从版本 11.1.4 开始 — — 都存在漏洞! 代码不再位于同一位置,漏洞利用的逻辑也略有变化。 如前所述,从版本 12.2 开始,文件不再包含下划线,必须简单命名为 middleware.ts。此外,它不再位于 pages 文件夹中(这对我们来说很方便,因为从版本 13 开始,引入了 app 路由器,这本会使可能性加倍)。 With that in mind, the payload for the first versions starting with version 12.2 is very simple: 考虑到这一点,从版本 12.2 开始的第一个版本的有效负载非常简单:x-middleware-subrequest: middleware /src 目录还需要考虑到 Next.js 提供了创建 /src 目录的可能性: (Next.js documentation) 作为在项目根目录中拥有特殊 Next.js app 或 pages 目录的替代方法,Next.js 还支持将应用程序代码放在 src 目录下的常见模式。(Next.js 文档) 在这种情况下,payload 将是:x-middleware-subrequest: src/middleware 因此,无论路径中有多少层级,总共只有两种可能性。这简化了针对相关版本的漏洞利用难度。 在最新版本中,它又有了一点变化(我们保证,最后一次)。最大递归深度在更新的版本中,逻辑又略有变化,请看这段代码:v15.1.7 和之前一样,系统会检索 x-middleware-subrequest 头部的值,并使用冒号作为分隔符形成一个列表。但这次,请求直接转发的条件 — — 即忽略中间件规则 — — 有所不同: 常量depth的值必须大于或等于常量 MAX_RECURSION_DEPTH 的值(即 5)。在赋值过程中,每当列表 subrequests(即由:分隔的头部值)中的某个值等于 params.name(即中间件的路径)时,常量depth就会增加 1。如前所述,这里只有两种可能性:middleware 或 src/middleware。 因此,为了绕过中间件,我们只需要在请求中添加以下头部/值:x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware 或x-middleware-subrequest: src/middleware:src/middleware:src/middleware:src/middleware:src/middleware 这段代码最初是用来做什么的? 这段代码似乎是为了防止递归请求陷入无限循环。 https://nextjs.org/blog/cve-2025-29927漏洞利用既然我们知道您喜欢这类内容,这里有一些来自 Bug Bounty Program 的真实案例。 绕过授权/重写 在这个例子中,当我们尝试访问/admin/login时,收到404响应。从响应头中可以看出,中间件执行了路径重写,以防止未经授权或不适当的用户访问:但使用我们的授权神器 :我们可以毫无障碍地访问该端点,中间件被完全忽略。目标 Next.js 版本:15.1.7 绕过 CSP 这次网站使用中间件来设置 — — 除了其他功能外 — — CSP和cookie:让我们绕过它:Target next.js version: 15.0.3Target next.js 版本:15.0.3 注意: 请留意两个目标的payload差异,其中一个使用了src/目录,而另一个没有。 通过缓存投毒实现 DoS(What?) 是的,通过这个漏洞也可能实现缓存投毒 DoS 攻击。这显然不是我们首先要寻找的,但如果没有敏感路径受到保护,且没有更有趣的可利用点,那么某些情况可能会导致缓存投毒拒绝服务(CPDoS): 假设一个网站根据用户地理位置重写用户路径,添加(/en、/fr等),且没有在根路径(/)上提供页面或资源。如果我们绕过中间件,就会避开重写,最终到达根页面。由于开发者并未打算让用户访问根页面,因此没有提供相应页面,我们会得到404(或根据重写配置/类型不同,可能是500)。 如果该网站使用了缓存/CDN 系统,可能会强制缓存404响应,导致页面不可用,严重影响站点可用性。澄清 自安全公告发布以来,我们收到了一些人的咨询,他们担心自己的应用程序安全,并且不太理解攻击的范围。需要明确的是,易受攻击的元素是中间件。如果您没有使用中间件(或至少没有将其用于敏感目的),那么无需担心(不过,请检查上面提到的 DoS 方面),因为绕过中间件不会绕过任何实际的安全机制。 否则,后果可能是灾难性的,我们建议您迅速实施安全公告中的指导措施。安全公告 — CVE-2025–29927补丁对于 Next.js 15.x,此问题已在 15.2.3 中修复对于 Next.js 14.x,此问题已在 14.2.25 中修复对于 Next.js 版本 11.1.4 到 13.5.6,我们建议查阅以下解决方法。解决方案 如果无法升级到安全版本,我们建议你阻止包含 x-middleware-subrequest 请求头的外部用户请求访问你的 Next.js 应用。 严重性 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N(严重程度:9.1/10,临界级) https://github.com/vercel/next.js/security/advisories/GHSA-f82v-jwr5-mffw更多信息在撰写本文时,部署在 Vercel 和 Netlify 上的应用显然已不再受此漏洞影响(更新:由于存在大量误报,Cloudflare 已将该规则调整为仅在用户主动启用时生效 — — 这些误报未能有效区分来自合法用户的请求与潜在攻击者的请求)。 https://x.com/nextjs/status/1903522002431857063免责声明本研究发布仅用于教育目的,旨在帮助开发者理解问题的根本原因,或为研究人员 / 漏洞猎人在未来的研究工作中提供启发。本文作为安全公告的补充材料,提供了有关漏洞本质的进一步说明和澄清 — — 因为公告中已公开了导致该漏洞的请求头(以及相关的提交差异)。 我们明确声明不支持对本文进行任何不道德的使用。结语正如本文所强调的,这个漏洞在 Next.js 源代码中已经存在了数年,随着中间件及其版本的演变而变化。任何软件都可能出现严重漏洞,但当它影响到最流行的框架之一时,就变得特别危险,可能对更广泛的生态系统造成严重后果。如前所述,在撰写本文时,Next.js 每周下载量接近 1000 万次。它广泛应用于从银行服务到区块链等关键领域。当漏洞影响到用户依赖的成熟功能(如授权和身份验证)时,风险就更大了。 Vercel 团队花了几天时间来解决这个漏洞,但值得注意的是,一旦他们意识到问题,修复就被提交、合并到几小时内实现到新版本中(包括向后移植)。 编译 | Yewlne 编辑 & 排版 | Yewlne、环环 ## Publication Information - [LXDAO](https://paragraph.com/@lxdao/): Publication homepage - [All Posts](https://paragraph.com/@lxdao/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@lxdao): Subscribe to updates - [Twitter](https://twitter.com/LXDAO_Official): Follow on Twitter