The Header That Skips the Check: Bypassing Next.js Middleware
The challenge
Mereton's internal console puts its login wall in Next.js middleware: every request to /admin is supposed to pass through it first. A researcher noticed the framework trusts a header it uses to talk to itself, and that nothing stops you from sending that header from the outside. The request to the admin dashboard is loaded in the editor. Make the middleware stand aside and read the admin API key the dashboard prints once you are in.
What you'll learn
- Explain why a value a framework uses to trust itself must never be accepted from a client
- Reproduce the x-middleware-subrequest bypass by sending the header with a repeated value
- Read a redirect response for the clue that names the middleware being skipped
- Connect this to the X-Forwarded-For rule: trust what your edge writes, not what arrives
- Recognise authentication enforced only in middleware as a single point that can be stepped around
Skills tested
Prerequisites
- The shape of an HTTP request
- What middleware does in a web framework
How it works
A web framework often makes requests to its own internals, and marks them with a header so it does not loop forever. Next.js used x-middleware-subrequest for exactly that. The problem was that the same header decided whether the middleware ran: the framework counted how many times the current middleware's name appeared in it, and once the count reached its recursion limit of five, it assumed a loop and skipped the middleware.
Nothing stripped that header at the edge, so a browser could send it too. Set it to the middleware's own module path, repeated five times, and the authentication layer simply did not run. The request reached the protected page as if it had been approved.
The general rule is that a token meaning this request is internal must be unforgeable from outside, which in practice means the boundary has to remove any inbound copy and write its own. A header, by itself, is never that.
Common mistakes
- Sending a single
middleware. Older Next.js releases skipped on one copy, but this version counts them and only stands aside at its recursion limit of five. - Editing the path or the Host. The endpoint and host are correct. Only the trust header is the way in.
- Reading the login redirect as a dead end. Its comment names the middleware module, which is the value you need.
- Answering with the redirect target.
/login?next=/adminis the wall, not the prize.
How to defend against it
Treat the header as hostile input.
- Strip
x-middleware-subrequestat the CDN or reverse proxy so it can never arrive from a client. The framework's patched releases do this internally; upgrade. - Do not make authentication depend on a single middleware pass. Re-check authorisation in the route handler or data layer, where a skipped middleware cannot help.
- Apply the same discipline to every trust header (
X-Forwarded-For,X-Real-IP, internal auth headers): the only value you may trust is the one your outermost proxy wrote.
Full solution
Community stats
Related Daily Hacks
Credited contributors
These hackers reported issues, improved content, and helped harden this page.