webfreehard

funikuler-vragam-kubani

alfactf

Task: a Next.js 16 / React 19 canary service exposed a hidden server action, but a WAF blocked the usual Next-Action header. Solution: smuggle the action id in HTTP trailers, adapt the React2Shell multipart graph to the canary quoting rules, get RCE, then read the receipt file containing the flag.

$ ls tags/ techniques/
filesystem_enumerationrsc_action_recontrailer_header_bypasshidden_server_action_invocationreact2shell_rcefile_read_via_rce

$ cat /etc/rate-limit

Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.

funikuler-vragam-kubani — alfactf

Description

Service: funikuler-vragam-kubani

English summary: the target was https://funicular-gm2cxozn.alfactf.ru/, a Next.js 16.0.6 / React 19 canary application. The goal was to reach the hidden recovery flow, turn it into code execution, and read the file that stored the flag.

Analysis

Summary

The exploit chain was:

  1. leak a hidden server action id from the RSC stream,
  2. bypass the WAF by moving Next-Action into HTTP trailers,
  3. confirm and invoke the hidden recoveryAction,
  4. pivot to React2Shell / CVE-2025-55182,
  5. fix the multipart encoding for this React canary build,
  6. gain RCE as root, enumerate files, and read the receipt with the flag.

Recon

Initial reconnaissance showed a modern App Router stack: Next.js 16.0.6 with React 19 canary. The important clue was inside the RSC response, where the server exposed a hidden action id:

4082c44f4a6a9cc400f0e6b45ed1c06c10f100aad2

That meant the backend still had a callable server action even though the UI did not expose it.

WAF bypass with HTTP trailers

Sending a normal Next-Action header was blocked by the WAF, so direct invocation failed before the request reached Next.js. The workaround was to send a chunked request with:

Trailer: Next-Action

and then place the actual action id in the trailer section after the terminating chunk. This passed the WAF but still reached the backend parser.

Validation was easy: using a fake action id returned 404 together with:

x-nextjs-action-not-found: 1

So the trailer value was definitely being consumed as the real Next-Action header by the backend.

Hidden action behavior

...

$ grep --similar

Similar writeups