Summary:
CVE-2025-29927 is a critical (CVSS 9.1) authorization bypass vulnerability in all versions of Next.js from version 1.11.4 → 15.2.2. The bug is because the client is spoofing the x-middleware-subrequest HTTP header, which is only supposed to be set for the framework's internal subrequests; if the header is present, Next.js bypasses all middleware checks, assuming that the request "has already passed through middleware". This bypasses security checks such as authentication/role checking and allows the attacker to access secret routes or APIs without a session.
Branch | Affected Versions | Patched Version |
---|---|---|
12.x | ≤ 12.3.4 | 12.3.5 |
13.x | ≤ 13.5.8 | 13.5.9 |
14.x | ≤ 14.2.24 | 14.2.25 |
15.x | ≤ 15.2.2 | 15.2.3 |
Setting up the Vulnerable Environment:
To test this vulnerability, we will use the vulnerable sample application available on the CVE-2025-29927 github link. This will allow us to learn what Middleware does based on code.
git clone https://github.com/aydinnyunus/CVE-2025-29927
cd CVE-2025-29927
npm install
npm run build
node .next/standalone/server.js
Now that our test system is ready, we can move on to the next step to fully understand the vulnerability.
PoC (Proof of Concept) and Exploit
The Next.js framework utilizes middleware functions called Middleware to route requests within the application. Middleware can be used for a variety of operations, it can provide user authentication, response editing and routing. Next.js uses a special HTTP header to avoid infinite loops within internal requests: x-middleware-subrequest This header signals that a request has already been processed by a Middleware, therefore the same request will not go through the Middleware again. This allows it to bypass items such as authentication.
A Few Use Cases of Middleware:
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
const MAINTENANCE = process.env.MAINTENANCE === 'true';
export function middleware(request: NextRequest) {
if (MAINTENANCE && !request.nextUrl.pathname.startsWith('/maintenance')) {
return NextResponse.rewrite(new URL('/maintenance', request.url));
}
return NextResponse.next();
}
The code block above shows the redirection process of a website that is under maintenance. In this type of code structure, the maintenance work page can be skipped directly.
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
const WINDOW = 60_000; // 1 dakika
const LIMIT = 30; // 30 istek
// Bellekte tutulan [ip => {count, reset}]
const hits: Record<string, { count: number; reset: number }> = {};
export function middleware(request: NextRequest) {
const ip = request.ip || 'unknown';
const now = Date.now();
const entry = hits[ip] ??= { count: 0, reset: now + WINDOW };
if (now > entry.reset) {
entry.count = 0;
entry.reset = now + WINDOW;
}
entry.count++;
if (entry.count > LIMIT) {
return new NextResponse('Too Many Requests', { status: 429 });
}
return NextResponse.next();
}
export const config = { matcher: ['/api/:path*'] };
In this code block, a middleware is used to prevent the rate limit vulnerability. In this case, it allows it to be bypassed.
Let's first identify where the vulnerability is originating from the next.js side before we proceed to test our vulnerable application.
Download the source code from next.js/v12.0.7 and open the same within your code editor. Next, go to Packages>next>server file path and open next-server.ts file. Go to the 689-714 code line range where the real vulnerability starts and the middleware header is being directly trusted.
If we analyze the code block. The header value received in code lines 689-687 is assigned to the subrequests directory without checking. Then, between code lines 691-696
If subrequests.includes(
middlewareInfo.name
)
condition is true:
-
result =
NextResponse.next
()
(i.e. “Middleware is already running” assumption) -
continue
skips the rest of the loop.
this way it is not directly inserted into the middleware loop and is skipped.
Let's try these operations in the vulnerable application we have installed now. First, let's try to go to the protected
page.
If we send a request to the protected
path, we can see that it redirects directly with status code 307. So which code block in our application causes this situation. If we look, we can see the following lines of code.
This time, let's put the x-middleware-subrequest: middleware
header and send /protected
request again.
We omitted the redirect on the /protected
page so we can see the page content.
Different uses of Header:
if you want to bypass the middleware in the path /dashboard/panel/admin. You have 3 possibilities respectively:
pages/_middleware
pages/dashboard/_middleware
pages/dashboard/panel/_middleware
src/middleware
Next.js v15.2.3 Loop Bypass:
In the v15.2.3 version of Next.js, the middleware can be bypassed thanks to the value received from the header. This is due to the 5 repetitive loop control.
x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware
x-middleware-subrequest: src/middleware:src/middleware:src/middleware:src/middleware:src/middleware
is over-ridden by the header instance above. Okay, if we're forced to review why this is happening, well, first off, let's download the source code for v15.2.3 of the github account and review where the vulnerability is occurring.
Open the source code in any code editor and go to packages>next>src>server>web>sandbox file path and start analyzing the sandbox.ts file
In the above given code snippet, we have conventional uncontrolled reading of the header and listing of the sub-requests. After that, depth is set to 5 in order to prevent infinite looping. And then check is done about how many times the header value has been written. If the same value is exceeded or equaled the value in the headers given above as an example, it causes you to skip the middleware directly and the content is shown.
References:
https://zhero-web-sec.github.io/research-and-things/nextjs-and-the-corrupt-middleware