skip to content

Early HTTP/2 work (SPDY) compressed header fields with gzip, and that approach was abandoned. What attack made generic compression of HTTP headers unsafe, and how does HPACK handle sensitive header values?

level: seniorimportance: should knowfreq 30%

answer

  1. compress secret + attacker text = length oracle
  2. CRIME 2012: TLS compression + SPDY gzip headers
  3. HPACK matches whole field values, no substrings
  4. never-indexed literal, intermediaries must honour it
  5. BREACH = same idea on gzipped response bodies, still live

basics

~20 s

CRIME. When a secret (a session cookie) and attacker-chosen text are compressed together, the compressed size leaks whether they match, letting the attacker guess the secret one character at a time through TLS. HPACK removes substring matching - it indexes whole field values only - and adds a never-indexed representation for secrets.

solid answer

~60 s

SPDY compressed the header block with DEFLATE. DEFLATE finds *arbitrary repeated substrings*, so if an attacker can inject a chosen string into a request that also carries the victim's `Cookie`, the output shrinks slightly more when the guess matches a cookie prefix. TLS hides the content but not the length, so the attacker learns one character per few hundred requests - that is CRIME (2012). HPACK is designed so that this side channel is far weaker: - It only ever matches **whole `name: value` pairs** against table entries. There is no partial or substring match, so a guessed prefix produces no size signal. - Huffman coding is a **static, context-free** code, so its output length depends on the characters present, not on what else is in the block. - For values the sender knows are secret, the **never-indexed literal** representation says: send this literally, do not store it, and no intermediary may index it either. An attacker who can control an entire header value can still probe for exact matches, which is why never-indexed exists at all. BREACH - the same trick against gzipped *response bodies* - is unrelated and still live.

code

http · 7 lines
http
GET /?sessionid=a HTTP/1.1        # compressed block includes both...
Cookie: sessionid=9f2c4b1e         # ...the guess and the secret

# guess 'a'  -> compressed header block: 412 bytes
# guess 'b'  -> compressed header block: 412 bytes
# guess '9'  -> compressed header block: 411 bytes   <-- prefix matched
# fix first char = '9', repeat for the next character

go deeper

for a junior

Know the name CRIME and the one-line mechanism: compressed size leaks whether an attacker's guess matches the secret, so gzip on headers was dropped.

for a middle

Explain why DEFLATE's substring matching gives a per-character oracle and how HPACK's whole-field matching removes it.

for a senior

Add the residual exact-match oracle, the never-indexed representation and its obligation on intermediaries, and separate CRIME from BREACH with the right mitigations for each.

for a principal

Generalise to a design rule - attacker-controlled input must not share a compression context with a secret - and audit for it across headers, bodies, logs and storage.

## The general principle Compression removes redundancy. If a secret and attacker-controlled data are compressed *together*, the size of the output is a function of how similar they are. Encryption hides content but, for a stream cipher or any mode without heavy padding, not length. Therefore: **never compress attacker-influenced data together with a secret in a channel whose length the attacker can observe.** ## CRIME, concretely CRIME (Compression Ratio Info-leak Made Easy, 2012) targeted TLS-level compression and SPDY's DEFLATE-compressed header block. The setup: 1. The victim's browser holds a session cookie for `bank.example`. 2. The attacker gets JavaScript running on any page (an ad, an unrelated site) that can cause requests to `bank.example` - the cookie rides along automatically. 3. The attacker controls part of the request - the path, or a header value. The attacker makes the browser request a path containing `sessionid=a`, then `sessionid=b`, and so on. DEFLATE looks for repeated byte sequences anywhere in its window. When the guessed prefix matches the beginning of the real `Cookie: sessionid=...`, DEFLATE emits a back-reference instead of literals and the encrypted record is a byte or two shorter. The attacker watches ciphertext lengths on the wire, keeps the shortest, and extends the guess one character at a time. That is linear, not exponential: a 20-character token falls in a few thousand requests. The response was blunt: TLS compression was disabled everywhere, and SPDY's gzip header compression was replaced during HTTP/2 standardisation. HPACK exists because the working group needed compression that was still worth having but did not hand out an oracle. ## How HPACK narrows the channel **No substring matching.** DEFLATE's power - and its danger - is that it matches arbitrary byte runs across the whole window. HPACK matches only at field granularity: either the complete `name: value` pair equals a table entry or it does not. A guess that shares 15 of 20 characters with the real cookie compresses exactly the same as a guess that shares none. The per-character oracle disappears. **Static Huffman code.** The Huffman table is fixed in the RFC, not adaptive. The encoded length of a string is a function only of its own characters, never of what appeared earlier in the block. There is a residual, very weak signal - encoded length varies with which characters a value contains - but it does not compose into a practical prefix oracle. **Never-indexed literals.** The remaining risk is exact-match probing: if an attacker can cause an entire header field value to be emitted, they can test whether that exact value is already in the dynamic table by watching whether it encodes to one byte or many. It is a much coarser oracle than CRIME's, but real for low-entropy values. HPACK therefore defines the never-indexed representation, which carries two obligations: the encoder must not insert the field into its dynamic table, and every intermediary that re-encodes the message **must** preserve the never-indexed representation rather than deciding to index it. Encoders apply it to `Authorization`, sometimes `Cookie`, CSRF tokens and similar credentials. ## Practical takeaways - Understand it as a class of bug, not one CVE: any place where a secret and attacker text share a compression context is suspect - header compression, response-body gzip, compressed logs, compressed database columns. - **BREACH** is the sibling attack against gzipped *HTTP response bodies* containing both a CSRF token and reflected user input. HPACK does nothing about it. Mitigations are different: keep secrets out of compressible responses, mask tokens per response, or disable compression on sensitive endpoints. - Length hiding is a partial defence. TLS record padding and HTTP/2 frame padding blur sizes, but neither is a substitute for not sharing the compression context. - Do not conclude 'compression is dangerous, turn it off'. The correct rule is about *shared context with attacker-controlled input*, and HPACK demonstrates that a format can be designed to keep most of the win while removing most of the oracle. ## What an interviewer is checking They want to hear that you can reason about a side channel: compression ratio is observable through encryption, an attacker who supplies part of the input can search a secret character by character, and the fix was a format change (field-granularity matching) plus an explicit opt-out (never-indexed) rather than a configuration knob.

  • Is HPACK completely immune to compression side channels?
    No, it is hardened, not immune. An attacker who can cause an entire header field value to be sent can still learn whether that exact value is already in the dynamic table, because a hit encodes to about one byte and a miss to many. That coarse exact-match oracle is why the never-indexed representation exists, and why credentials should use it.
  • Does HPACK protect against BREACH?
    No. BREACH attacks gzip-compressed HTTP response bodies that contain both a secret such as a CSRF token and reflected attacker input. That is the body, not the header block, so header compression is irrelevant. Mitigations are separate: keep secrets out of compressible responses, mask the token per response, or disable body compression on the affected endpoints.

Like guessing a password by watching how much a suitcase shrinks when you pack your guess next to the real thing: identical socks nest, so a smaller suitcase means you guessed right.

saying these in an interview costs you the question

  • Saying the fix was 'TLS encrypts it, so length does not matter'
  • Confusing CRIME (headers and TLS compression) with BREACH (gzipped response bodies)
  • Claiming HPACK is provably immune to compression oracles
  • Thinking never-indexed merely means 'do not store locally', missing the obligation on intermediaries
  • Concluding that all compression must be disabled rather than isolating attacker-controlled input from secrets

context