GHSA-9998-894r-fwvr: High severity maven/org.http4s:http4s-ember-core_3 vulnerability
Summary
Ember's HTTP/1.1 header parser matches the Transfer-Encoding header value with a case-sensitive substring test (hValue.contains("chunked")). RFC 9112 §7 requires transfer-coding names to be compared case-insensitively. A request carrying Transfer-Encoding: Chunked (capital C) is therefore not recognised as chunked, and Ember falls back to framing by Content-Length (or zero if absent) while a compliant intermediary frames the same bytes by chunked encoding. The two parsers then disagree on where the request body ends, enabling HTTP request smuggling (TE.CL / TE.0).
The same line of code admits two further variants:
- The substring test misfires on Transfer-Encoding: notchunked (an inverse desync — Ember treats it as chunked while a compliant intermediary rejects the unknown coding). - Header field bytes are decoded with the platform-default charset. Under UTF-8 the wire bytes E2 84 AA decode to U+212A KELVIN SIGN, which String.equalsIgnoreCase Unicode-case-folds to k, so Transfer-Encoding: chun<U+212A>ed matches chunked once the comparison is made case-insensitive without also pinning the decode to ISO-8859-1.
Impact
Server
Request smuggling when ember-server is an origin behind an intermediary that honours Transfer-Encoding case-insensitively per RFC, forwards the header value verbatim, and reuses keep-alive connections to the backend:
- Front-end security bypass: the smuggled request reaches paths the intermediary's ACL/auth layer would have blocked, with attacker-chosen method and headers. - Cross-user request hijack: a partial smuggled prefix left in Ember's connection buffer is concatenated with the next victim's request on the same pooled backend connection, exposing its headers (e.g. Cookie, Authorization) to the attacker. - Cache poisoning: the smuggled response is associated with the next request key in a caching proxy.
Client
ember-client shares the same HeaderP.parse on the response path, enabling response smuggling when http4s is used as a gateway. This is less severe: it requires a malicious or compromised upstream rather than an anonymous remote client.
Preconditions
- Unauthenticated remote attacker (server) - ember-server as origin behind a keep-alive intermediary - Intermediary treats Transfer-Encoding case-insensitively (per RFC) and forwards the header value without lowercasing it - Malicious or compromised upstream (client)
Workarounds
- Intermediary fully buffers and re-encodes request bodies (e.g. nginx with default proxyrequestbuffering on) - Intermediary normalises the Transfer-Encoding value (lowercases the token) before forwarding - Disable backend keep-alive between the intermediary and Ember
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
maven/org.http4s:http4s-ember-core_3to a version that resolves this vulnerability.Fixed in 1.0.0-M47 - Upgrade
Upgrade
maven/org.http4s:http4s-ember-core_2.13to a version that resolves this vulnerability.Fixed in 1.0.0-M47 - Upgrade
Upgrade
maven/org.http4s:http4s-ember-core_2.12to a version that resolves this vulnerability.Fixed in 0.23.35 - Upgrade
Upgrade
maven/org.http4s:http4s-ember-core_2.13to a version that resolves this vulnerability.Fixed in 0.23.35 - Upgrade
Upgrade
maven/org.http4s:http4s-ember-core_3to a version that resolves this vulnerability.Fixed in 0.23.35
Event History
Frequently Asked Questions
Which deployments are realistically exposed?
Deployments using the Ember HTTP/1.1 header parser are exposed when a request can pass through a compliant intermediary that interprets Transfer-Encoding differently. The affected artifacts are org.http4s:http4s-ember-core for Scala 2.12, 2.13, and 3.
What does an attacker need to send to trigger the parser disagreement?
An attacker can use a Transfer-Encoding value whose handling differs between Ember and a compliant parser, such as "Chunked" with a capital C. The issue can cause Ember to use Content-Length framing, or zero-length framing when Content-Length is absent, while the intermediary uses chunked framing.
Are there payload variants beyond mixed-case "Chunked"?
Yes. "Transfer-Encoding: notchunked" can make Ember treat an unknown transfer coding as chunked, while a compliant intermediary rejects it. With UTF-8 platform-default decoding, the Kelvin-sign byte sequence can also produce a Unicode character that case-folds to "k" in a chunked token comparison.