Spread
Spread is a one-developer side project for viewing CSVs — sign up, upload a file, and read it as a tidy table. It runs behind an edge proxy that keeps the instance's internal ops console off the public internet. Your objective is to reach that console and read the instance's service token.
The Scenario
Spread is the kind of tool someone builds in a weekend: accounts, CSV upload, a clean table, a profile photo, a settings page. To save time, the author put the ops console at /admin and "secured" it the easy way — by blocking that path at the edge proxy rather than adding a login, trusting that the app is only ever reachable through the proxy. The service token shown on that console is the flag. Everything the app itself does is buttoned up; the gap is in how the front and back of the stack read an HTTP request.
Challenge Intel
Synopsis
The edge proxy and the app's HTTP server disagree on how to find the end of a request body — the edge trusts Content-Length, the app trusts Transfer-Encoding: chunked. That CL.TE desync lets a request smuggled inside a chunked profile-photo upload reach the edge-blocked /admin console.
What It Is
/admin (and /internal) is blocked at the edge proxy but has NO application login — the app trusts that only the edge can reach it. The edge delimits each request body by Content-Length and forwards bytes verbatim upstream over a pinned keep-alive connection; the app's front (a small HTTP server in front of the Flask app) delimits by Transfer-Encoding: chunked. Send POST /api/v1/profile-upload (the avatar endpoint — a multipart upload where a chunked body is unremarkable) carrying BOTH a Content-Length and Transfer-Encoding: chunked. The body is a single zero-size chunk (`0\r\n\r\n`) followed by a smuggled `GET /admin/ HTTP/1.1` request. The edge forwards the whole Content-Length body; the app ends the body at the zero-chunk and treats the smuggled bytes as the next request on the connection. Pipeline a normal follow-up request (e.g. `GET /`) on the same connection — its response is the /admin console, which renders the service token (the flag). A direct GET /admin returns the edge's 403 ("restricted to the internal network"), which is the tell that a proxy — not the app — guards the path.
Who It's For
Players ready for their first HTTP request-smuggling desync. You should be comfortable hand-crafting raw HTTP over a single keep-alive connection (curl --data-binary, netcat, a socket script, or Burp Repeater) and reading more than one response off one connection.
Skills You'll Practice
- Spotting that an internal path is blocked at a proxy, not the app (edge 403 vs app 404)
- Recognising a CL.TE setup: front trusts Content-Length, back trusts chunked
- Hand-crafting a chunked body that hides a second request after the zero-chunk
- Pipelining a follow-up request to collect the smuggled response
What You'll Gain
- A working mental model of front-end/back-end request desynchronization
- Why 'block it at the load balancer' is not access control
- The CL.TE payload skeleton, reusable against real smuggling targets