CVE-2025-29927: Understanding the Next.js Middleware Vulnerability

CVE-2025-29927: Understanding the Next.js Middleware Vulnerability

CVE-2025-29927

Take a look at the Next.js middleware vulnerability CVE-2025-29927.

CVE-2025-29927 is a security issue that can bypass Next.js middleware authorization. Next.js uses the x-middleware-subrequest header to prevent infinite recursion during middleware execution.

If a user injects this header value through a proxy tool, middleware authorization can be invalidated, allowing access to critical server resources and potentially sensitive data.

Middleware and the vulnerability

Middleware is a common concept in frameworks like Rails and Laravel to reuse logic that would otherwise be duplicated on every page when handling incoming requests. Next.js middleware is a little different. It is a place to perform simple checks and routing before server component code executes, and it is not appropriate as the only authentication mechanism for an app.

After responding to this issue, the Next.js team removed authentication-related use cases for middleware from the official docs.

Middleware is fine for simple checks like the presence of a cookie value, but it is not appropriate to serve as the only auth component for the entire app.

export function middleware(request) {
  const sessionCookie = request.cookies.get('session');
  if (!sessionCookie) return Response.redirect('/login');
}

Validation that checks the database or verifies cookie validity should be performed in server components as well.


// lib/auth.ts
export async function validateSession(token?: string) {
  if (!token) return null
  
  const user = await db.user.findUnique({
    where: { sessionToken: token },
    select: { id: true, name: true, role: true }
  })

  return user
}



// app/dashboard/page.tsx
import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'
import { validateSession } from '@/lib/auth'

export default async function DashboardPage() {
  // Get cookies from headers
  const cookieStore = cookies()
  const sessionCookie = cookieStore.get('session')?.value

  // Validate session on server
  const user = await validateSession(sessionCookie)

  // Redirect if validation fails
  if (!user) {
    redirect('/login')
  }

  // Render protected content
  return (
    <div>
      <h1>Welcome, {user.name}</h1>
      {/* Protected content */}
    </div>
  )
}

x-middleware-subrequest

The x-middleware-subrequest header value is not intended to be injected from outside the server and is used internally by Next.js to prevent infinite recursion between middleware calls. Internally, Next.js splits the header value on colons to create a list, then compares the count to MAX_RECURSION_DEPTH. If the count meets or exceeds the threshold, middleware is bypassed.

CVE-2025-29927 vulnerability mechanism
export const run = withTaggedErrors(async function runWithTaggedErrors(params) {
  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',
        },
      }),
    }
  }
}

Demo

Demo code: https://github.com/dante01yoon/CVE-2025-29927

The scenario is as follows. The /admin page should not be visible to requests that do not include the auth cookie, so the middleware checks for the cookie.

Here is the middleware code.

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // Check if the request is for the admin page
  if (request.nextUrl.pathname.startsWith('/admin')) {
    // Check for the authentication cookie
    const isAuthenticated = request.cookies.has('auth');
    
    if (!isAuthenticated) {
      // Redirect to login page if not authenticated
      return NextResponse.redirect(new URL('/login', request.url));
    }
  }

  return NextResponse.next();
}

// Define which paths this middleware should run on
export const config = {
  matcher: ['/admin/:path*'],
}; 

With the vulnerable 15.2.2 version, sending a request to /admin with a specific header value via curl will skip middleware and return the page.

x-middleware-subrequest: src/middleware:src/middleware:src/middleware:src/middleware:src/middleware

From 15.2.3 with the patch applied, the same request is redirected to /login.

Next.js versions affected by the vulnerability

Because most older versions are exposed, you should update Next.js quickly to address the vulnerability. The affected versions are:

  • Next.js 11.1.4 through 13.5.6

  • Next.js 14.x before 14.2.25

  • Next.js 15.x before 15.2.3

Applications not affected

If you do not use middleware or you host on Vercel/Netlify using their managed services, your application is not affected by this issue. However, many companies use self-hosting, so you should still patch by upgrading.

Alternatives to upgrading

There are cases where you cannot update due to compatibility issues.

CVE-2025-29927 mitigation options
  • When self-hosting Next.js, put a web server such as Nginx in front of the Next.js server and return an error for requests that include the x-middleware-subrequest header.
server {
    listen 80;
    server_name yourdomain.com;

    location / {
        # Block requests containing the x-middleware-subrequest header
        if ($http_x_middleware_subrequest != "") {
            return 403;
        }
        
        # Proxy the request to your Next.js application
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
Comments0
No comments yet.

Related Posts