REDHAT-BUG-2536964: Medium severity maven/io.netty/netty-codec-http vulnerability
Netty split Transfer-Encoding fields bypass final-chunked validation and enable request smuggling
A public GitHub Security Advisory (GHSA-3jrc-fchc-59pw) describes the following issue:
Summary
pov: pov-test
Netty's HTTP/1.1 decoder added RFC 9112 validation for malformed Transfer-Encoding order in the fix family for GHSA-38f8-5428-x5cv / CVE-2026-42585. That validation rejects the single-field case Transfer-Encoding: chunked, identity, but it can be bypassed when equivalent ordering is expressed as multiple header fields and the last field value is a short non-final transfer coding such as gzip or deflate.
For example:
text Transfer-Encoding: chunked Transfer-Encoding: gzip
is equivalent to the combined field value:
text Transfer-Encoding: chunked, gzip
In that value, chunked is not the final transfer coding. A request recipient must reject this message under RFC 9112 framing rules. Netty instead treats the message as chunked, accepts the terminating zero-size chunk, and parses a following request as a second request on the same connection.
This creates an HTTP request-smuggling primitive when Netty is paired with an intermediary or peer that rejects, buffers, or differently interprets the same invalid transfer-coding order.
This should be triaged as a current supported-release bypass/incomplete fix in the malformed Transfer-Encoding family. It is not a claim that malformed split Transfer-Encoding risk was never discussed publicly: Netty issue #9861 raised closely related behavior in 2019, and the May 2026 GHSA-38f8-5428-x5cv advisory fixed a single-field instance of the same ordering rule. The current issue is that latest supported releases still accept the split-field form after those fixes.
Technical Details
HttpUtil.isTransferEncodingChunked(...) returns true if any Transfer-Encoding field contains the comma-separated token chunked:
text codec-http/src/main/java/io/netty/handler/codec/http/HttpUtil.java:362 codec-http/src/main/java/io/netty/handler/codec/http/HttpUtil.java:363
HttpHeaders.containsValue(...) checks comma-separated values across header fields:
text codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java:1600 codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java:1603
Once any chunked value is found, HttpObjectDecoder enters chunked decoding. It then validates only the final Transfer-Encoding field value:
text codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java:853 codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java:859 codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java:861
The validation has a length guard:
text codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java:863 codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java:865 codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java:867
The comment says that if the final value is not longer than chunked, Netty knows it is only chunked. That assumption is false for final values like gzip and deflate. As a result:
- Transfer-Encoding: chunked, identity is rejected. - Transfer-Encoding: chunked followed by Transfer-Encoding: gzip is accepted. - Transfer-Encoding: chunked followed by Transfer-Encoding: deflate is accepted.
Git blame ties this validation block to the May 4, 2026 hardening commit:
text 977661f71f7511ad68ca17cabd7b5185efb978f4 Motivation: RFC9112 specified that chunked must be the last encoding so we should verify this.
PoC
Run the local PoV:
fish cd oss-zero-day-harness bash submission-bundle/netty-pov-test-transfer-encoding-order-bypass/poc/reproduce.sh
The key request shape is:
text POST /one HTTP/1.1\r\n Host: target\r\n Transfer-Encoding: chunked\r\n Transfer-Encoding: gzip\r\n \r\n 0\r\n \r\n GET /two HTTP/1.1\r\n Host: target\r\n \r\n
Observed on current 4.2:
text CASE splitfieldschunkedthengzipaccepted request uri=/one result=success transferEncoding=[chunked, gzip] lastContent bytes=0 result=success request uri=/two result=success transferEncoding=[] lastContent bytes=0 result=success
The same run confirms the patched single-field case is rejected:
text CASE singlefieldchunkedthenidentityrejected request uri=/one result=failure(java.lang.IllegalArgumentException:chunked must be the last encoding present in the Transfer-Encoding header) transferEncoding=[chunked, identity]
The supplementary Content-Length case is rejected on current 4.2:
text CASE splitfieldschunkedthengzipwithcontentlengthrejected request uri=/one result=failure(io.netty.handler.codec.http.ContentLengthNotAllowedException:Content-Length are not allowed in HTTP/1.1 messages that contains a Transfer-Encoding header.) transferEncoding=[chunked, gzip]
Impact
HTTP request smuggling can bypass front-end routing and access-control decisions, desynchronize request processing, or cause a following request to be processed in an unintended context. The exploitability of this primitive depends on a deployment with a parser differential, but Netty is the component that accepts the malformed framing and splits the stream into two requests.
The included PoV also checks a Content-Length variant. Current Netty rejects split Transfer-Encoding plus Content-Length with ContentLengthNotAllowedException. This report therefore does not claim the exact CL.TE exploitation shape from GHSA-38f8-5428-x5cv; it claims the TE-only split-field final-coding bypass that remains in current supported releases.
The PoV is local-only. It uses EmbeddedChannel and HttpRequestDecoder; it does not contact any live service.
Suggested Fix
Parse Transfer-Encoding as one ordered list across all field lines before deciding whether to enter chunked decoding. The decoder should reject HTTP/1.1 requests when the final transfer-coding to
[truncated]
Affected: - maven:io.netty:netty-codec-http affected >=4.2.12.Final; fixed unknown - maven:io.netty:netty-codec-http affected >=4.1.132.Final; fixed unknown
Fixed versions: see advisory
Advisory: https://github.com/netty/netty/security/advisories/GHSA-3jrc-fchc-59pw
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Compensating control
Deploy a compensating request-framing mitigation (e.g., normalize/reject malformed HTTP/1.1 Transfer-Encoding header ordering at the front-end/proxy) because Netty may accept split-field Transfer-Encoding ordering that can enable request smuggling when paired with a parser differential.
Event History
Frequently Asked Questions
What request format is needed to trigger the parsing discrepancy?
The request must use multiple Transfer-Encoding header fields so that chunked is followed by a non-final coding in a later field, such as “Transfer-Encoding: chunked” followed by “Transfer-Encoding: gzip” or “deflate.” Netty treats this as chunked and accepts a terminating zero-size chunk even though RFC 9112 requires rejection.
When can this become a request-smuggling issue?
The issue creates a request-smuggling primitive when Netty is paired with an intermediary or peer that handles the malformed Transfer-Encoding ordering differently. After accepting the zero-size chunk, Netty can parse following bytes as a second request on the same connection.