Optimization #9173
openhttp: a multi-value Content-Encoding header silently disables HTTP body inspection
Description
Hi,
I have a detection-evasion defect to report in Suricata's HTTP/1 body decompression (rust/htp, libhtp-rs). Confirmed on 8.0.7 RELEASE, and I believe it affects main as well.
The short version: when Content-Encoding carries more than one value and one of those values is a token the list path does not know, that decompression layer gets dropped while the rest of the chain still runs. Suricata inflates the outer layer, hands the still-compressed inner stream to the body hooks, and inspects it as though it were plaintext. "br" is in the fast path but missing from the list path, so "Content-Encoding: br, gzip" over gzip(br(P)) is enough. On main, "zstd" has the same problem.
What that costs: for the affected transaction every rule on http.response_body, http.request_body and file_data stops matching at once, in both directions. This is not one signature being dodged. The only trace is an ABNORMAL_CE_HEADER applayer anomaly, and nothing about it says that body inspection was lost.
I verified it in two environments:
- Offline replay of the attached pcap on 8.0.7 (jasonish/suricata:8.0.7). Four transactions with byte-identical plaintext once decoded, differing only in Content-Encoding. Three alerts instead of four. For the "br, gzip" transaction, 8,514 bytes reach inspection instead of 65,536.
- A public instance of mine running 8.0.7 with the stock ET Open ruleset. No configuration changed and no rules added; I observed through signatures that were already loaded. Same result over the wire, both directions.
The messages themselves are fine. The test endpoint decodes every variant back to the full 65,536-byte plaintext, so the encoding combination is valid and both ends agree on what it means.
I also wrote the regression test as a suricata-verify test, since that seems to be what you prefer. It asserts four alerts and gets three, so it is red today and should go green with the fix. I ran it with your runner under the same container image rather than working the numbers out by hand; the output is in the report.
Two things in the report worth flagging, because they cost me time and might cost yours: http.length in EVE is the wire body length, so it cannot distinguish a healthy transaction from an evaded one (fileinfo.size can). And my first reproducer was a live capture that came out truncated, which looks exactly like "does not reproduce".
Severity: I would put this at HIGH under your policy. The feature is Tier 1 and enabled by default, and the evasion has a wide scope.
Attachments:
REPORT.md
Root cause with file and line references for both affected functions (response.rs and request.rs), observation tables for both directions, EVE samples, suggested fixes, the regression test, and the measurement traps I hit.
REPORT.html
The same report, browser-readable.
f2-repro.pcap
102 packets, 99,937 bytes, sha256
4b0422fe75f5dddcde717812dfe48fb253106436603fcd0179b974e42aab1b53
Four HTTP response transactions, byte-identical plaintext, differing only in Content-Encoding: absent / gzip / br / "br, gzip". The sessions are synthesised with full handshakes and complete bodies, so nothing is lost in transit. Replay with
docker run --rm --entrypoint /usr/bin/suricata -v $PWD:/d \
jasonish/suricata:8.0.7 -r /d/input.pcap -S /d/test.rules -l /d/out -k none
Not confidential. Please feel free to put it in CI or QA.
http-content-encoding-multivalue.tar.gz
The suricata-verify test: test.yaml, test.rules, input.pcap, README.md. Red on 8.0.7, expected to go green once this is fixed.
f2-evidence.tar.gz
Raw EVE and logs behind the numbers above, including the suricata-verify runner output.
Credit as Zer0Gate, if that works for you.
Thanks,
Zer0Gate
Credit¶
Zer0Gate
Files
JI Updated by Jason Ish 7 days ago
@Philippe Antoine You think public optmization ticket?
PA Updated by Philippe Antoine 7 days ago
- Tracker changed from Security to Optimization
- Status changed from New to Triaged
- Target version changed from TBD to 9.0.0-beta1
- Disclosure Date deleted (
12/23/2026)
Yes, this behavior is intentional so as to avoid compression bombs
I am not sure we want to allow this even behind a congif option which is not by default...
But we can have a more precise anomaly than ABNORMAL_CE_HEADER
PA Updated by Philippe Antoine 7 days ago
- Private changed from Yes to No