Next.js中间件高危漏洞CVE-2025-29927:原理、复现与修复
1. 项目概述最近在梳理Next.js应用的安全边界时一个编号为CVE-2025-29927的漏洞引起了我的注意。这个漏洞的官方评级是“高危”CVSS 9.1核心问题出在Next.js框架的中间件Middleware逻辑上。简单来说它允许攻击者通过构造一个特殊的HTTP请求头就能让本该执行安全检查的中间件“罢工”直接放行请求。这意味着如果你的应用依赖中间件来做用户登录验证、管理员权限检查或者路径重写那么这个防护层就可能形同虚设。我花了些时间搭建环境、调试代码把这个漏洞的来龙去脉和利用方式彻底摸了一遍。这篇文章我就以一个一线开发兼安全研究者的视角带你从零开始把这个漏洞的成因、复现过程、利用手法以及背后的设计缺陷掰开揉碎了讲清楚。无论你是Next.js开发者想加固自己的应用还是安全爱好者想了解漏洞挖掘的思路相信都能从中获得一些实用的东西。2. 漏洞原理深度剖析2.1 Next.js中间件的工作机制与安全假设要理解这个漏洞首先得明白Next.js中间件是干什么的以及它默认是怎么保护自己的。中间件是Next.js提供的一个非常强大的功能它允许你在请求到达页面Page或API路由API Route之前先执行一段你写的代码。这段代码就像一个“安检门”常见的用途包括身份验证Authentication检查请求的Cookie或Token判断用户是否登录。如果没登录就重定向到登录页或者直接返回401错误。路径重写Rewriting根据一些条件比如用户语言偏好动态修改请求的路径。例如把/blog重写成/en/blog。安全头设置给响应自动添加一些安全相关的HTTP头比如Content-Security-Policy (CSP) 来防范XSS攻击。机器人检测Bot Detection识别并拦截恶意的爬虫流量。从架构上看中间件运行在一个独立的“沙箱”环境中。当一个HTTP请求到达Next.js服务器时流程是这样的请求先被交给中间件函数处理中间件函数执行完毕后可以决定是继续向后传递请求调用NextResponse.next()还是直接返回一个响应比如重定向或错误。这里就引出了一个关键问题如果中间件本身的代码里又发起了新的、指向同一个Next.js应用的请求这个新请求会不会再次触发中间件答案是会。这就会导致无限递归。想象一下你的中间件里有一行fetch(‘/api/auth’)来验证用户而这个/api/auth请求本身又会经过同一个中间件中间件又去调用/api/auth…… 这就死循环了。Next.js框架的设计者显然意识到了这个问题。他们的解决方案是引入一个内部标记机制。当一个请求是由中间件内部发起的即一个“子请求”时框架会自动给这个请求加上一个特殊的HTTP头x-middleware-subrequest。中间件逻辑在运行时会检查这个头。如果发现当前请求带有这个头就说明它是“自己人”发起的内部调用理论上应该跳过某些检查或者直接放行以避免递归。这里就埋下了第一个安全隐患框架默认假设x-middleware-subrequest这个头是可信的只有框架内部才能设置。这个假设在理想情况下成立但一旦外部攻击者能够伪造并发送这个头整个信任链条就崩溃了。2.2 漏洞核心递归深度检测的逻辑缺陷漏洞的具体触发点在于Next.js框架为了防止递归过深比如代码写错了导致中间件无限调用自己实现了一套递归深度检测机制。相关逻辑位于Next.js源码的packages/next/src/server/web/sandbox/sandbox.ts文件中以v15.2.2版本为例。我们来拆解一下这段关键的逻辑提取标识每个中间件在构建时都会被分配一个唯一的逻辑路径标识params.name。这个标识可能是middleware如果中间件文件在项目根目录也可能是src/middleware如果中间件文件在src目录下。解析计数当请求到达时框架会读取请求头中的x-middleware-subrequest值。这个值是一个用冒号:分隔的字符串。框架将这个字符串分割成数组然后遍历数组计算数组中与当前中间件标识params.name相等的项有多少个。这个数量就是所谓的“递归深度”。深度判断框架设定了一个最大递归深度常量MAX_RECURSION_DEPTH其值为5。如果计算出的深度大于等于5框架就认为递归过深出于保护目的它会返回一个特殊的响应。这个响应的关键点在于它包含一个响应头x-middleware-next: ‘1’。绕过发生当中间件运行时如果它接收到一个来自框架的、带有x-middleware-next: ‘1’头的响应它会将这个信号解读为“跳过所有后续中间件逻辑直接放行请求到后端路由”。漏洞的利用链条就此清晰攻击者从外部直接发送一个HTTP请求。 在这个请求中手动设置x-middleware-subrequest头其值为重复了5次或以上的中间件标识字符串例如middleware:middleware:middleware:middleware:middleware。 Next.js框架收到这个请求执行上述深度检测逻辑。因为它检测到x-middleware-subrequest头中包含了5次middleware标识深度 5于是框架误以为这是一个因内部错误导致的、递归过深的内部请求。 框架出于“保护”目的生成并返回那个带有x-middleware-next: ‘1’头的响应。 中间件看到这个头便跳过了所有安全检查如验证登录状态将请求直接传递给后端的页面或API路由。 攻击者成功绕过鉴权访问到了受保护的资源。注意这里有一个非常关键且容易混淆的点。攻击者发送的请求其x-middleware-subrequest头是给框架看的用于欺骗框架的递归检测逻辑。而框架返回的x-middleware-next头是给中间件运行时看的用于指示中间件跳过逻辑。攻击者并不直接构造x-middleware-next头而是通过构造x-middleware-subrequest头来“诱使”框架生成它。2.3 为什么是5次标识符如何确定你可能会问为什么偏偏是5次这其实是一个工程上选择的阈值目的是在防止无限递归和允许合理的内部调用之间取得平衡。比如一个中间件调用另一个API那个API又触发了一个重写理论上可能会有几层的嵌套。5次对于正常应用来说足够深了一旦超过大概率是代码逻辑错误框架选择强制中断。另一个关键问题是攻击者如何知道中间件的标识符params.name是什么这个标识符并不是保密的它实际上由Next.js的构建过程决定并记录在构建产物中。具体位置在.next/server/middleware-build-manifest.json文件里。在开发模式下甚至可以通过一些错误信息推测出来。因此对于部署的应用攻击者是有可能通过信息收集比如分析构建文件如果存在泄露或常见路径猜测middleware,src/middleware来获取这个标识符的。3. 漏洞复现环境搭建与利用3.1 搭建一个存在漏洞的Next.js应用理论讲完了我们动手来复现。首先我们需要一个存在漏洞的Next.js版本。受影响的版本范围是10.0.0, 15.2.3。我们就用漏洞爆出前的15.2.2版本。# 1. 创建一个新的Next.js项目 npx create-next-app15.2.2 vulnerable-app cd vulnerable-app # 2. 创建一个简单的鉴权中间件 # 在项目根目录创建 middleware.tsmiddleware.ts 内容如下import { NextResponse } from next/server import type { NextRequest } from next/server export function middleware(request: NextRequest) { // 模拟鉴权逻辑检查cookie中是否有authToken const authToken request.cookies.get(authToken)?.value // 如果用户未登录没有authToken则重定向到登录页 // 假设 /dashboard 是受保护页面/login 是登录页 if (!authToken request.nextUrl.pathname.startsWith(/dashboard)) { const loginUrl new URL(/login, request.url) return NextResponse.redirect(loginUrl) } // 如果已登录或者访问的是公开页面则放行 return NextResponse.next() } // 配置中间件匹配的路由 export const config { matcher: /dashboard/:path*, // 保护所有以/dashboard开头的路径 }创建受保护页面app/dashboard/page.tsxexport default function DashboardPage() { return ( div h1超级机密管理后台/h1 p只有登录用户才能看到这个页面/p p这里是敏感数据.../p /div ) }创建登录页app/login/page.tsxuse client; import { useRouter } from next/navigation; export default function LoginPage() { const router useRouter(); const handleLogin () { // 模拟登录设置一个cookie document.cookie authTokensecret-token-123; path/; alert(登录成功); router.push(/dashboard); }; return ( div h1登录页面/h1 button onClick{handleLogin}模拟登录/button /div ); }现在我们的应用逻辑很简单访问/dashboard会被中间件拦截检查authTokencookie。如果没有就跳转到/login。登录后设置cookie才能访问/dashboard。3.2 构造绕过攻击正常情况下未登录直接访问http://localhost:3000/dashboard会被重定向到/login。现在我们使用工具如curl、Postman或Burp Suite来发送恶意请求。首先我们需要确定中间件的标识符。由于我们的middleware.ts文件在项目根目录在Next.js 15.2.2中其标识符就是middleware。如果文件在src目录下标识符则是src/middleware。使用curl发起攻击curl -v -H “x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware” “http://localhost:3000/dashboard”关键点分析-H参数用于设置HTTP请求头。x-middleware-subrequest头的值是由5个middleware用冒号连接而成。我们请求的是受保护的/dashboard路径。预期的成功响应如果漏洞利用成功你将不会收到302重定向到/login的响应而是直接收到/dashboard页面的HTML内容状态码200就像你已经登录了一样。这意味着中间件的鉴权逻辑被完全绕过。3.3 利用场景扩展这个漏洞的危害不仅限于绕过登录。任何依赖中间件实现的安全或业务逻辑都可能被绕过角色权限绕过中间件根据用户角色如user,admin限制访问/admin/*路径。攻击者绕过后可直接访问管理员接口。地理封锁Geo-blocking绕过中间件根据IP地区限制访问内容。绕过后被屏蔽地区的用户可以访问。API速率限制绕过在中间件中实现的简单计数器限流逻辑失效。路径重写/代理逻辑绕过可能导致应用逻辑错乱访问到非预期的后端服务。实操心得在测试时浏览器的开发者工具Network标签可能不会显示你手动添加的x-middleware-subrequest头因为浏览器可能会出于安全原因限制某些头的设置。因此使用curl、Postman或专业的渗透测试工具如Burp Suite是更可靠的选择。在Burp Suite中你可以轻松地拦截请求然后修改或添加这个头部。4. 漏洞修复方案与代码审计启示4.1 官方修复方案分析Next.js团队在15.2.3版本中修复了此漏洞。修复的核心思路是打破“x-middleware-subrequest头是可信的”这个危险假设引入一个不可伪造的令牌Token来验证内部请求的真实性。我们来看关键的修复代码位于packages/next/src/server/web/sandbox/sandbox.ts及相关文件// 修复后的逻辑简化示意 if ( header ‘x-middleware-subrequest’ headers[‘x-middleware-subrequest-id’] ! (globalThis as any)[Symbol.for(‘next/middleware-subrequest-id’)] ) { delete headers[‘x-middleware-subrequest’] }修复原理拆解引入动态令牌框架在启动时会在一个全局的、使用Symbol.for(‘next/middleware-subrequest-id’)创建的唯一存储空间中放入一个随机生成的ID令牌。Symbol.for确保了这个存储空间的全局唯一性难以被外部代码模拟或访问。内部请求标记当一个请求真正由框架内部发起时即合法的子请求框架除了会添加x-middleware-subrequest头还会额外添加一个x-middleware-subrequest-id头其值就是上面那个动态令牌。严格校验在处理任何请求时框架会检查x-middleware-subrequest头。如果存在这个头它会进一步检查是否同时存在有效的x-middleware-subrequest-id头并且其值是否与全局存储的令牌匹配。无效即删除如果x-middleware-subrequest-id不存在或不匹配框架就认定这个x-middleware-subrequest头是外部伪造的直接将其从请求头对象中删除。这样一来后续的递归深度检测逻辑看到的x-middleware-subrequest头就是空的或者不完整的无法达到深度5的触发条件自然也就不会生成x-middleware-next: ‘1’的响应。这个修复方案非常经典和有效。它相当于给内部通信加了一个“口令”外部攻击者无法知晓或生成这个动态口令因此无法伪造合法的内部请求。升级建议所有使用Next.js 10.0.0至15.2.2版本的项目应立即升级到15.2.3或更高版本。这是最直接、最安全的修复方式。4.2 临时缓解措施如果因为某些原因无法立即升级可以考虑以下临时缓解措施但请注意这些都不是根本解决方案自定义请求头验证在中间件的最开始加入一段检查代码如果发现请求包含x-middleware-subrequest或x-middleware-next头且来源可疑比如不是来自本地网络或可信IP则直接拒绝请求。// middleware.ts - 临时缓解代码示例 import { NextResponse } from ‘next/server’ import type { NextRequest } from ‘next/server’ export function middleware(request: NextRequest) { // 检查是否存在伪造的内部请求头 const subRequestHeader request.headers.get(‘x-middleware-subrequest’); const middlewareNextHeader request.headers.get(‘x-middleware-next’); // 如果存在这些头并且请求不是来自可信内部源此处仅为示例逻辑需根据实际情况设计 // 例如可以检查IP地址、或一个自定义的密钥头 const isTrustedSource request.ip ‘127.0.0.1’; // 简单示例仅信任本地 if ((subRequestHeader || middlewareNextHeader) !isTrustedSource) { // 直接返回403禁止访问 return new NextResponse(‘Forbidden’, { status: 403 }); } // ... 原有的鉴权逻辑 ... }注意这种方法依赖于准确识别“可信源”在复杂的部署环境如云原生、多Pod部署中可能难以实施且可能被攻击者通过IP欺骗等方式绕过。强烈建议仅作为升级前的临时手段。Web应用防火墙WAF规则在应用前端的WAF或反向代理如Nginx上配置规则直接丢弃或拦截包含x-middleware-subrequest头的传入请求。这可以阻止大部分外部攻击但需要运维团队配合。4.3 对开发者的安全启示CVE-2025-29927给所有开发者尤其是框架使用者上了一堂生动的安全课永远不要信任客户端输入这是安全领域的黄金法则。HTTP请求头、Cookie、URL参数、POST数据所有来自客户端的东西都是不可信的都必须经过严格的验证和过滤。Next.js最初的设计错误就在于默认信任了一个本应由服务端控制的头。理解框架的“魔法”像Next.js这样的高阶框架为了开发者体验做了很多“自动”的事情。作为开发者我们有责任去理解这些自动化机制背后的原理和潜在的安全边界。比如中间件是如何被调用的内部重定向是如何处理的深度防御Defense in Depth不要只依赖一层安全防护。中间件鉴权很好但关键的后端API路由自身也应该进行权限校验。这样即使中间件被绕过还有第二道防线。关注安全公告积极关注你所使用框架和库的安全邮件列表、GitHub发布页或安全社区。像这样的高危漏洞官方通常会有及时的通报和补丁。5. 漏洞挖掘与审计的延伸思考5.1 如何发现此类逻辑漏洞CVE-2025-29927属于典型的“逻辑绕过”漏洞。挖掘这类漏洞通常需要以下几步理解正常流程首先你需要彻底弄清楚系统正常工作的流程。对于Next.js中间件就是请求如何被拦截、检查、放行或拒绝。寻找状态标识关注那些用于控制流程的“状态标识”。在这个漏洞里x-middleware-subrequest和x-middleware-next就是关键的状态标识头。任何用于内部通信、区分请求类型的标记都是潜在的审计点。挑战假设问自己一个问题“如果这个标识被外部控制了会怎样” 框架假设某个头只有自己能设置我们就尝试手动设置它。框架假设某个参数有固定格式我们就尝试畸形的格式。追踪数据流在代码审计中追踪这些标识是如何被生成、传递、验证和消费的。在Next.js的案例中我们需要追踪x-middleware-subrequest从哪里来应该是内部添加到哪里去被sandbox.ts读取并用于深度计算最终影响了什么决策返回x-middleware-next头。构造边界条件漏洞往往出现在边界条件处理上。这里的边界条件就是“递归深度等于5”。框架的本意是处理“深度5”的内部错误但因为没有验证头的来源导致外部输入也能触发这个边界条件从而走向非预期的分支跳过鉴权。5.2 针对Next.js生态的进一步安全检查点基于这个漏洞的模式我们可以对Next.js应用进行更广泛的安全审视其他内部头除了x-middleware-subrequestNext.js是否还使用了其他类似的内部头例如x-middleware-rewrite,x-middleware-redirect,x-forwarded-*系列头等。尝试伪造这些头观察应用行为是否出现异常。构建产物信息泄露.next目录下的构建文件如middleware-build-manifest.json,routes-manifest.json是否可能被错误配置公开访问这些文件包含了路由、中间件标识等敏感信息是攻击者进行精准攻击的“地图”。自定义服务器Custom Server的安全如果项目使用了server.js自定义服务器需要额外小心。自定义服务器绕过了Next.js的许多内置安全处理和优化开发者需要手动实现许多中间件功能更容易引入逻辑错误。环境变量与配置检查next.config.js中的安全相关配置如headers配置项是否正确地设置了安全响应头如CSP、HSTS避免因为配置不当导致其他漏洞。5.3 自动化工具辅助手动审计效率有限可以借助一些工具静态代码分析SAST使用工具扫描Next.js项目源代码寻找潜在的不安全代码模式比如直接使用未经验证的请求头。动态应用安全测试DAST使用漏洞扫描器如OWASP ZAP, Burp Suite Professional对部署的应用进行自动化扫描。可以配置扫描策略重点测试自定义的HTTP头注入。依赖检查使用npm audit或yarn audit定期检查项目依赖确保Next.js框架本身及其依赖库没有已知漏洞。CVE-2025-29927的复现和分析过程再次印证了安全是一个持续的过程而非一劳永逸的状态。作为开发者保持对底层原理的好奇心对客户端输入的不信任感是构建稳健应用的重要基石。这次漏洞的修复也展示了开源社区响应速度及时跟进官方更新是成本最低的安全投资。