Frontend Labs
⋯

Part 6 — Advanced web and API exploitation

G1 Part 10 covered the OWASP classes: injection, XSS, CSRF, SSRF, broken access control, auth. This Part is the graduate seminar: the bug classes that need protocol-level understanding, that chain into RCE or account takeover, and that the first guide deliberately left out. Every chapter is exploit-then-fix, and the practice targets are PortSwigger Web Security Academy (which has a dedicated, free, labbed track for almost every topic here) plus apps you deploy so you can also remediate.

Chapter 30 — HTTP request smuggling and desync

In one sentence

When a front-end (CDN, load balancer, reverse proxy) and a back-end disagree about where one HTTP request ends, an attacker can prepend bytes to the next person's request — bypassing front-end security, stealing other users' requests, and poisoning responses.

Where we are

Modern web serving is layered: browser -> CDN -> load balancer -> app server, often with HTTP/2 in front and HTTP/1.1 behind. Every hop re-parses the request. Smuggling exploits parsing disagreements.

How it works

The classic ambiguity: Content-Length vs Transfer-Encoding. A request can declare its body length two ways. If a front-end honours one and the back-end the other, bytes "leak" past the boundary.

   CL.TE  -- front-end uses Content-Length, back-end uses Transfer-Encoding:
     POST / HTTP/1.1
     Host: victim
     Content-Length: 6
     Transfer-Encoding: chunked

     0

     G
   Front-end: body is 6 bytes ("0\r\n\r\nG"), forwards the whole thing.
   Back-end: chunked; "0\r\n\r\n" ends the request; "G" is left in the
     buffer and PREPENDED to the next request on that connection ->
     "GPOST / HTTP/1.1 ..." (a broken method for the victim), or a fuller
     payload smuggles a complete request.

   TE.CL  -- front-end uses TE, back-end uses CL: mirror image, chunk-size
     line games.

   TE.TE  -- both do TE, but one is tricked into NOT processing it via
     obfuscation:
       Transfer-Encoding : chunked          (space before colon)
       Transfer-Encoding: xchunked
       Transfer-Encoding:\tchunked
       Transfer-Encoding\n : chunked
       (two TE headers, one valid one junk)

HTTP/2 makes it worse via downgrade. HTTP/2 has an unambiguous, explicit length — so h2-to-h2 is safe. But a front-end that speaks h2 to the client and downgrades to h1 for the back-end must synthesise Content-Length/TE, and:

   H2.CL / H2.TE   the h2 request declares one length; the front-end
     rewrites to h1 with a conflicting CL or an injected TE that the
     back-end honours.
   H2 CRLF INJECTION   h2 header names/values can contain bytes that,
     when serialised to h1, become "\r\n" -> the attacker injects entire
     headers or a request line -> request splitting.
   H2 request smuggling via header-name/pseudo-header abuse.

Client-side desync (James Kettle, 2022): the browser itself is the smuggling engine. A malicious page makes a fetch() whose body is left straddling the connection boundary; the next request the browser sends on that reused connection is poisoned — no back-end proxy required, and it works against the victim's own authenticated session.

Impact:

   - bypass front-end controls: smuggle a request to /admin past a WAF /
     auth proxy that only inspected the outer request.
   - capture other users' requests: smuggle a request whose body is a
     large parameter; the victim's following request gets appended into
     that parameter and (if the app stores/reflects it) you read their
     headers, cookies, CSRF tokens.
   - response queue poisoning: desync the response stream so User B gets
     User A's response (or your injected one) -> mass credential/session
     theft, cache poisoning (Ch 31).
   - forced browser cache poisoning / persistent XSS via the above.

Detection: timing probes (a partial smuggled request makes the next request hang for the socket timeout — a reliable differential), and differential responses. Tools: Burp HTTP Request Smuggler extension, smuggler.py, h2csmuggler, Burp's "Turbo Intruder" for the concurrency.

Worked example

Bypassing a front-end auth check.

   Front-end proxy blocks /admin unless the request has a valid staff
   session; forwards everything else. Back-end trusts the front-end.

   CL.TE smuggle:
     POST /feedback HTTP/1.1
     Host: shop.securesh.op
     Content-Length: 116
     Transfer-Encoding: chunked

     0

     GET /admin/delete?user=victim HTTP/1.1
     Host: shop.securesh.op
     X-Ignore: X
   Front-end: sees /feedback (allowed), CL=116, forwards all bytes.
   Back-end: chunked -> request ends at "0\r\n\r\n"; the rest
     ("GET /admin/delete...") starts the NEXT request -- which the
     back-end processes WITHOUT the front-end ever seeing "/admin".
     The next legitimate user's request completes the smuggled one (or
     provides its Host/cookies). Admin action executed.

   FIX:
     - Use HTTP/2 end to end; do NOT downgrade to h1 at the back hop.
     - If you must downgrade: the front-end must reject any request with
       BOTH Content-Length and Transfer-Encoding, or with a malformed/
       obfuscated TE, or with header-name/value bytes that don't
       round-trip -- normalise or 400.
     - Back-end: don't trust the front-end for auth; re-check on the
       back-end (assume-breach, Ch 3).
     - Disable back-end connection reuse across different client
       connections, or bind a back-end connection to one client -> a
       smuggled prefix can't reach another user.
     - Consistent, strict HTTP parsers on every hop (same library/config).

Practice (90 min)

  1. PortSwigger Academy "HTTP request smuggling" track — do CL.TE, TE.CL, TE.TE, obfuscation, then the H2 downgrade and CRLF-injection labs, then the response-queue-poisoning and request-capture labs, then client-side desync.
  2. Install Burp's HTTP Request Smuggler; run its scan against a lab; read how the timing probe works.
  3. Stand up a deliberately-mismatched stack locally: an old nginx/haproxy in front of a permissive back-end (or use the http-garden / smuggler research harness). Reproduce CL.TE. Then fix by upgrading/normalising and re-test.
  4. Configure an h2 front-end that downgrades to h1; try H2.CL. Then set it to h2 end-to-end and confirm the attack dies.

Across the series

  • Reproduce the root cause safely: the HTTPS guide's desync lab sends one message with both Content-Length and Transfer-Encoding to a Go backend, which handles it as two requests. It also shows why a Go handler can't detect the conflict, and why re-serialising proxies help.

Common mistakes

  • Front-end downgrading to HTTP/1.1 for the back-end. The single biggest enabler of modern smuggling. Go h2 end-to-end.
  • Trusting the front-end for security decisions. Re-authenticate and re-authorise on the back-end.
  • Accepting requests with both CL and TE, or with obfuscated TE. Reject (400), don't "pick one".
  • Reusing back-end connections across client connections. Lets a smuggled prefix land on someone else's request. Bind or disable.
  • Different HTTP parsers/configs on each hop. Normalise; use the same strict library everywhere.
  • Assuming a WAF stops it. The WAF is a hop too, and often the one being bypassed.

Check yourself

  1. What disagreement makes CL.TE smuggling work, and what ends up in the back-end's buffer?
  2. Why is HTTP/2 end-to-end safe, and how does an h2->h1 downgrade reintroduce smuggling?
  3. What is client-side desync and why doesn't it need a back-end proxy?
  4. Name three impacts of a successful smuggle beyond "bypass a filter".
  5. Give four defences (front-end normalisation, transport, connection handling, back-end trust).

(Answers: Appendix G.)

Further reading

  • Research: PortSwigger — "HTTP Desync Attacks: Request Smuggling Reborn" (2019), "HTTP/2: The Sequel is Always Worse" (2021), "Browser-Powered Desync Attacks" (2022) — James Kettle. The definitive material.
  • Labs: PortSwigger Academy request-smuggling track (free).
  • Tools: Burp HTTP Request Smuggler, smuggler.py (defparam), h2csmuggler (BishopFox), http-garden (differential HTTP parser fuzzer).
  • RFC: RFC 9112 (HTTP/1.1 message syntax) sections on message length; RFC 9113 (HTTP/2).

Generated from real-life-security-guide-v1.md · 8 Oct 2026 16:36