skip to content

An HTTP/1.1 request arrives carrying both a Content-Length header field and Transfer-Encoding: chunked. What do the framing rules say a recipient must do, and why does disagreement between a front-end proxy and a back-end server on this point create a security problem?

level: seniorimportance: should knowfreq 44%

answer

  1. Transfer-Encoding beats Content-Length, but both = malformed
  2. 400 and close the connection
  3. CL.TE / TE.CL / TE.TE desync
  4. Leftover tail prefixes the next victim's request
  5. Shared keep-alive connection is the prerequisite

basics

~20 s

A message must never carry both. Transfer-Encoding wins over Content-Length, but the message is treated as malformed: a server should reject it, and a proxy must not forward both fields. If a proxy honours one field and the back end honours the other, they disagree on where the request ends, and leftover bytes become a forged request prefixed onto the next user's request. That is request smuggling.

solid answer

~50 s

The rules are explicit: a sender must not send Content-Length together with Transfer-Encoding, and if a recipient sees both, Transfer-Encoding takes precedence and Content-Length must be ignored or removed. Because that combination only arises from a broken or hostile sender, a server should answer `400 Bad Request` and close the connection rather than guess. The danger is **desynchronisation across a hop**. Suppose a front-end proxy frames by Content-Length and the back-end frames by chunked (CL.TE), or vice versa (TE.CL). The proxy forwards what it believes is one request; the back end reads a shorter one and leaves the remaining bytes in its receive buffer. Those bytes are then treated as the start of the *next* request on that reused connection, and the next real user's request is appended to attacker-chosen headers and method. Defences: reject any message with both fields, prefer HTTP/2 end to end, normalise framing at the edge, and avoid connection reuse to the back end if the stack is unverified.

code

http · 9 lines
http
POST /search HTTP/1.1
Host: app.example.com
Content-Length: 6
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
X-Ignore: x

go deeper

for a junior

Know that the two framing fields must never appear together and that Transfer-Encoding takes precedence when they do.

for a middle

Add that recipients should reject with 400 and that intermediaries must not forward both, and explain a desync at the level of leftover bytes on a reused connection.

for a senior

Walk the CL.TE, TE.CL and TE.TE variants, the impact (auth bypass, request capture, cache poisoning), and mitigations ranked by leverage.

for a principal

Argue the systemic fix: one canonical parser across the chain, protocol upgrade end to end, explicit policy on connection reuse and downgrade, and treating lenient parsing as a security defect in itself.

## The precedence rule HTTP/1.1's message-length algorithm is ordered. Roughly: responses to HEAD and 1xx, 204, 304 have no body regardless of fields; otherwise, if `Transfer-Encoding` is present with `chunked` as the final coding, chunked framing applies and any `Content-Length` is ignored; otherwise a valid Content-Length applies; otherwise a request has no body and a response is close-delimited. So formally Transfer-Encoding wins. But the specification also forbids senders from combining the two, and instructs recipients to treat the combination as an error: a server should respond `400 Bad Request` and close the connection, and an intermediary must remove Content-Length before forwarding, or reject outright. The reason is not pedantry — it is that the only way both fields appear on the same request is a bug or an attack. Related ambiguities carry the same treatment: two `Content-Length` fields with different values, a Content-Length that is not a plain decimal number, or a Transfer-Encoding whose final coding is not chunked in a request. All are unrecoverable framing errors, not things to normalise into a best guess. ## How disagreement becomes an exploit HTTP request smuggling exploits two servers in a chain that resolve the ambiguity differently, while sharing one persistent connection between them. **CL.TE:** the front end frames by Content-Length and forwards the whole byte range; the back end frames by chunked and stops at the terminal `0` chunk. The bytes after that chunk stay in the back end's buffer and are prepended to whatever arrives next on that connection. **TE.CL:** the mirror image. The front end honours chunked, the back end honours Content-Length, and again a tail of attacker bytes is left over. **TE.TE:** both honour Transfer-Encoding in principle, but the attacker obfuscates the field (odd whitespace, duplicated field, a bogus additional coding) so that exactly one of them fails to recognise it and falls back to Content-Length. The leftover tail is chosen by the attacker to look like the beginning of a request: a method, a path, and headers with no terminating blank line. When the next victim's request arrives on the same connection, it is appended to that fragment. The consequences are severe: bypassing front-end authorisation and path-based access control (the front end never saw the smuggled path), capturing the victim's request — including their session cookie — into a body the attacker can read back, cache poisoning, and forced responses to other users. ## Defences that actually work 1. **Reject, do not normalise.** Any request with both fields, duplicate conflicting Content-Length, or malformed chunked syntax gets `400` and the connection is closed — closing matters, because leaving the connection open leaves the desynchronised buffer in play. 2. **Make the front end strict and identical to the back end.** Ambiguity is only exploitable when two parsers disagree; the fix is a single canonical parse, not two lenient ones. 3. **Prefer HTTP/2 (or HTTP/3) end to end.** Their framing is explicit in the binary layer, with length carried by DATA frames and END_STREAM, so the class of ambiguity largely disappears. Note the caveat: HTTP/2 front end downgrading to HTTP/1.1 upstream reintroduces it, since the proxy must synthesise Content-Length or chunked — this is the well-known H2.CL and H2.TE family. Do not let HTTP/2 pseudo-header values or a smuggled `transfer-encoding` field survive the downgrade. 4. **Limit connection reuse to the back end** where you cannot audit the stack. One connection per request removes the shared buffer that smuggling depends on, at a throughput cost. 5. **Reject requests carrying Transfer-Encoding over HTTP/2**, which the specification forbids anyway. ## How to talk about it A strong answer names the precedence rule, states that the correct response is rejection rather than resolution, explains the desync as a shared persistent connection whose two ends disagree about a message boundary, and lists mitigations in the order of highest leverage: uniform strict parsing, then protocol upgrade, then connection hygiene.

  • Why must the server close the connection after replying 400 to an ambiguous request, rather than keeping it alive?
    Because the framing error means the server no longer knows where the current message ended, so anything still buffered on that connection is of unknown provenance. Continuing to parse it would process attacker-controlled bytes as a fresh request, which is exactly the outcome the rejection was meant to prevent. Closing discards the ambiguous buffer.
  • Does terminating HTTP/2 at the edge eliminate request smuggling?
    Not by itself. If the edge downgrades to HTTP/1.1 upstream, it must generate framing for the back end, and a request smuggled through pseudo-headers, an injected Transfer-Encoding field, or a Content-Length that disagrees with the DATA actually sent can desynchronise that hop. The edge must validate that declared length matches delivered data and must strip connection-specific fields before downgrading.
  • Two Content-Length header fields arrive with the same value. Is that acceptable?
    It is tolerated by the length algorithm as equivalent to a single field with that value, but it is still a strong signal of a rewriting intermediary or an attack probe, and strict deployments reject it. Differing values are unambiguously an error and must be rejected.

saying these in an interview costs you the question

  • Saying you should just pick Content-Length because it is simpler
  • Treating the combination as something to sanitise and forward rather than reject
  • Answering 400 but keeping the connection alive
  • Believing smuggling needs a vulnerable application, rather than two disagreeing parsers
  • Claiming HTTP/2 at the edge makes smuggling impossible even with an HTTP/1.1 back end

context