Skip to content

Update netty.version [SECURITY] - #11

Open
renovate[bot] wants to merge 1 commit into
masterfrom
renovate/netty.version
Open

Update netty.version [SECURITY]#11
renovate[bot] wants to merge 1 commit into
masterfrom
renovate/netty.version

Conversation

@renovate

@renovate renovate Bot commented Jun 8, 2026

Copy link
Copy Markdown

ℹ️ Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Change Age Confidence
io.netty:netty-handler (source) 4.1.133.Final4.1.135.Final age confidence
io.netty:netty-transport-native-epoll (source) 4.1.133.Final4.1.135.Final age confidence
io.netty:netty-codec-http2 (source) 4.1.133.Final4.1.136.Final age confidence
io.netty:netty-codec-http (source) 4.1.133.Final4.1.136.Final age confidence
io.netty:netty-codec (source) 4.1.133.Final4.1.136.Final age confidence

Warning

Some dependencies could not be looked up. Check the Dependency Dashboard for more information.

Have a look at your dependency dashboard


Netty has an IPv6 Subnet Filter Bypass via Incorrect Comparator Masking

CVE-2026-44249 / GHSA-3qp7-7mw8-wx86

More information

Details

Summary

An attacker can bypass IPv6 subnet rules due to an incorrect masking operation in IpSubnetFilterRule.compareTo(). Valid public IP addresses can bypass the restrictions.

Details

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.

Impact

Access Control Bypass. Attacker can bypass IpSubnetFilter IPv6 access controls.

Severity

  • CVSS Score: 8.1 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Netty: SNI handler pre-allocates up to 16 MiB from nine attacker bytes

CVE-2026-45416 / GHSA-x4gw-5cx5-pgmh

More information

Details

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.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Netty: Wrapping plain trust manager silently disables hostname verification

CVE-2026-50010 / GHSA-c653-97m9-rcg9

More information

Details

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.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Netty: Unix-socket fd receive leaks descriptors when peer sends two at once

CVE-2026-45536 / GHSA-w573-9ffj-6ff9

More information

Details

netty_unix_socket_recvFd sets msg_control to char control[CMSG_SPACE(sizeof(int))] (line 940) — 24 bytes on 64-bit Linux. A peer-sent SCM_RIGHTS cmsg carrying two ints has cmsg_len = CMSG_LEN(8) = 24, which fits exactly with no MSG_CTRUNC, so the kernel installs both fds in the receiving process. The subsequent check cmsg->cmsg_len == CMSG_LEN(sizeof(int)) (line 972, expected 20) fails, the branch that would read the fd is skipped, and neither installed fd is closed. The for(;;) loop calls recvmsg again (non-blocking → EAGAIN → Java maps to 0 → read loop exits normally), leaving two leaked fds per message. There is no MSG_CTRUNC handling. Reachable via Epoll/KQueue DomainSocketChannel when the application opts into DomainSocketReadMode.FILE_DESCRIPTORS (non-default).

Severity

  • CVSS Score: 4.0 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Netty HTTP/2: Advertised MAX_CONCURRENT_STREAMS are not enforced

CVE-2026-47244 / GHSA-5x3r-wrvg-rp6q

More information

Details

Impact

DefaultHttp2Connection.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.

Resources

https://www.rfc-editor.org/rfc/rfc7540.html#section-6.5.2

Severity

  • CVSS Score: 5.3 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


netty-codec-http2: ByteBuf Reference-Count Leak in DelegatingDecompressorFrameListener Leads to Memory Exhaustion

CVE-2026-48043 / GHSA-c2gf-v879-257j

More information

Details

Impact

The 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.

A 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.

Severity

  • CVSS Score: 5.3 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Netty susceptible to HTTP/2 Reset Attack with different on-the-wire signature

CVE-2026-50560 / GHSA-563q-j3cm-6jxm

More information

Details

Summary

Netty HTTP/2 max header size handling produces attack similar to HTTP/2 Rapid Reset.

Details

There is a setting in the http2 specification called SETTINGS_MAX_HEADER_LIST_SIZE. According to the RFC: “This advisory setting informs a peer of the maximum field section size that the sender is prepared to accept, in units of octets.”

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
  • 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.

Remediation

When speaking with clients, Netty should potentially treat this as “advisory” and ignore it. It would be best to ignore the SETTINGS_MAX_HEADER_LIST_SIZE setting from clients (or ignore it when sending to clients). According to the spec, a server does not need to honor this advisory setting, and it appears that other http/2 implementations ignore it when acting as a server.

Impact

This is a DDoS attack similar to the HTTP/2 Rapid Reset Attack.

Credit

Jonathan Looney (Engineering, Netflix)

Contact

Ashley Tolbert (Security, Netflix) - artolbert@netflix.com

Severity

  • CVSS Score: 6.9 / 10 (Medium)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Netty: [codec-http2] Lack of Host Header Deduplication in HTTP/2→HTTP/1.x Translation Leads to Request Routing Bypass

CVE-2026-59900 / GHSA-c69g-56f8-xwqj

More information

Details

Netty's HTTP/2-to-HTTP/1.x translation layer (Http2StreamFrameToHttpObjectCodec and InboundHttp2ToHttpAdapter) fails to deduplicate or validate Host headers when an HTTP/2 client supplies both the :authority pseudo-header and a literal host header in a single HEADERS frame. The translator maps :authority to Host and separately copies the literal host header, producing an HttpRequest object containing two Host headers with attacker-controlled differing values.

Severity

  • CVSS Score: 6.9 / 10 (Medium)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Netty: HTTP/2 decompression leaks ByteBuf reference count when the decompressor channel is already closed (Direct memory leak / OOM DoS)

CVE-2026-56819 / GHSA-93wv-jw9v-4972

More information

Details

Summary

A remote, unauthenticated peer can leak one direct ByteBuf per HTTP/2 DATA frame in
applications that enable HTTP/2 content decompression via DelegatingDecompressorFrameListener.
When a DATA frame is processed for a stream whose decompressor has already been closed,
Http2Decompressor.decompress(...) retains the frame buffer but never releases it on the error
path, so its reference count never returns to zero. Repeating this over a long-lived HTTP/2
connection exhausts direct memory and crashes the JVM with OutOfMemoryError — a denial of service.

Details

In codec-http2/src/main/java/io/netty/handler/codec/http2/DelegatingDecompressorFrameListener.java,
Http2Decompressor.decompress(...) does:

// around line 433
decompressor.writeInbound(data.retain());

The argument data.retain() is evaluated before writeInbound(...) executes, incrementing the
buffer's reference count (refCnt: 1 -> 2). The very first statement of
EmbeddedChannel.writeInbound(...) is ensureOpen() (EmbeddedChannel.java:360), which throws
ClosedChannelException when the decompressor's internal EmbeddedChannel has already been closed.

When that happens:

  • the DATA payload has been retain()ed but never entered the pipeline, so the decoder's
    finally { release() } never runs;
  • the surrounding catch (Throwable t) block in decompress(...) (around line 451) does not
    release the extra reference;
  • the input buffer therefore can never reach refCnt 0, and its (typically direct) memory is leaked.

The decompressor channel is closed on a reachable path:
Http2Connection onStreamRemovedHttp2Decompressor.cleanup()
EmbeddedChannel.finishAndReleaseAll()
(DelegatingDecompressorFrameListener.java:125-133 and 418-420).

A peer that sends DATA frames for a stream whose decompressor has already been cleaned up (e.g.
continuing to send DATA after END_STREAM / stream removal) thus leaks one direct ByteBuf per
frame.

Affected code: DelegatingDecompressorFrameListener.java, method Http2Decompressor.decompress(...)
— the decompressor.writeInbound(data.retain()) call (line ~433) and its catch (Throwable t)
block (line ~451), which lacks a data.release() rollback.

Suggested fix: track whether writeInbound succeeded and roll back the extra retain() only when
the data never entered the pipeline:

boolean writeSucceeded = false;
try {
    decompressor.writeInbound(data.retain());
    writeSucceeded = true;            // pipeline now owns the release
    if (endOfStream) {
        decompressor.finish();
    }
    return 0;
} catch (Throwable t) {
    if (!writeSucceeded) {
        data.release();               // roll back the extra retain(); data never entered pipeline
    }
    if (t instanceof Http2Exception) {
        throw (Http2Exception) t;
    }
    throw streamError(stream.id(), INTERNAL_ERROR, t, ...);
}
Case writeSucceeded catch action Reason
ensureOpen() throws (this bug) false data.release() data never entered pipeline
handler throws internally true no release decoder finally already released
finish() throws true no release writeInbound already succeeded
PoC

Reproduced against the official, unmodified netty-codec-http2-4.2.15.Final.jar from Maven Central,
using real netty classes and measuring ByteBuf.refCnt() directly (the leaking logic is not mocked).

Reproduction steps:

  1. Download the official artifacts and their dependencies from Maven Central (version 4.2.15.Final):
    netty-common, netty-buffer, netty-transport, netty-resolver, netty-handler,
    netty-codec-base, netty-codec, netty-codec-http, netty-codec-http2,
    netty-codec-compression.
  2. Build a real Http2Decompressor wrapping a real gzip decoder EmbeddedChannel
    (ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP)).
  3. Close the internal decompressor channel (equivalent to the end state of
    cleanup() / finishAndReleaseAll()).
  4. Encode a real gzip DATA payload with ZlibCodecFactory.newZlibEncoder(GZIP) (refCnt = 1).
  5. Call decompress(...) on the closed channel.
  6. Observe: writeInbound(...) throws ClosedChannelException at its ensureOpen() entry
    (EmbeddedChannel.java:360), reached from DelegatingDecompressorFrameListener.java:433;
    data.refCnt() is now 2.
  7. Release once as the frame reader would; refCnt stays at 1 (release() returns false) → leaked.

Observed reference-count trace:

gzipData initial refCnt = 1
decompress -> data.retain()        -> refCnt = 2   (retain applied, never rolled back)
caller releases once               -> refCnt = 1   (release() returns false; not deallocated)
=> buffer never reaches 0 -> direct memory leaked

Observed exception stack (confirms the leak point):

java.nio.channels.ClosedChannelException
    at io.netty.channel.embedded.EmbeddedChannel.checkOpen(EmbeddedChannel.java:959)
    at io.netty.channel.embedded.EmbeddedChannel.ensureOpen(EmbeddedChannel.java:979)
    at io.netty.channel.embedded.EmbeddedChannel.writeInbound(EmbeddedChannel.java:360)
    at io.netty.handler.codec.http2.DelegatingDecompressorFrameListener$Http2Decompressor
            .decompress(DelegatingDecompressorFrameListener.java:433)

Two notes on the harness (they do not affect the leak mechanism):

  • The internal channel is closed directly via close() rather than through cleanup(). The end
    state is identical (channel closed → writeInbound throws at ensureOpen()); the bug depends on
    "channel closed → retain not rolled back", not on how the channel was closed.
  • In the isolated harness the rethrown StreamException's root cause shows as NullPointerException
    because the harness does not initialise an Http2LocalFlowController (a secondary exception
    reported during channel close). The leak is already sealed at the ClosedChannelException thrown
    by writeInbound's ensureOpen() (line 360); in a real server with the flow controller
    initialised, the triggering exception is the ClosedChannelException itself.

A complete self-contained PoC (Verify02DecompressLeak.java, ~150 lines, no test framework) plus the
exact javac / java commands can be attached on request.

Impact
  • Vulnerability type: uncontrolled resource consumption / memory leak (CWE-401), leading to
    denial of service. Each crafted DATA frame leaks one (typically direct/off-heap) ByteBuf.
  • Who is impacted: any server (or client) that enables HTTP/2 content decompression by installing
    DelegatingDecompressorFrameListener in its HTTP/2 pipeline.
  • Attacker requirements: remote, unauthenticated. The attacker only needs to send HTTP/2 DATA
    frames for a stream whose decompressor has been cleaned up (e.g. continue sending DATA after
    END_STREAM). No special server configuration beyond decompression being enabled.
  • Result: sustained triggering over a long-lived connection exhausts direct memory and crashes
    the JVM with OutOfMemoryError.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Netty: HttpObjectDecoder skips arbitrary initial control characters when only initial CRLF characters are permitted

CVE-2026-50020 / GHSA-hvcg-qmg6-jm4c

More information

Details

Summary

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.

Affected Code
File Lines Role
codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java 1298–1313 ISO_CONTROL_OR_WHITESPACE static initialiser — marks all ISO control chars
codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java 1307–1313 SKIP_CONTROL_CHARS_BYTES ByteProcessor — skips the entire set
codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java 1275–1289 LineParser.skipControlChars — advances readerIndex past all matching bytes
Specification Analysis
RFC 9112 §2.2 — Message Parsing

In the interest of robustness, a server that is expecting to receive and parse a
request-line SHOULD ignore at least one empty line (CRLF) received prior to the
request-line.

An HTTP/1.1 user agent MUST NOT preface or follow a request with an extra CRLF.

Deviation

The RFC names a single permitted exception: an empty line (bare CRLF, i.e. the two-byte
sequence \r\n). The ISO_CONTROL_OR_WHITESPACE table is initialised as:

for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
    ISO_CONTROL_OR_WHITESPACE[128 + b] =
        Character.isISOControl(b) || isWhitespace(b);
}

Character.isISOControl returns true for 0x000x1F and 0x7F. This includes NUL
(0x00), SOH (0x01), STX (0x02), BEL (0x07), DEL (0x7F), and every other non-CRLF
control character. The SKIP_CONTROL_CHARS state runs this scan unconditionally before the
first READ_INITIAL, meaning any sequence of such bytes prepended to a request is silently
consumed.

A load balancer or TLS terminator that does not perform the same scan sees a different
message boundary than Netty does, which is the basis of a request-desync / smuggling attack.

Suggested Unit Test

Add to HttpRequestDecoderTest.java.

@Test
public void testNonCrlfControlBytesPrecedingRequestLineAreRejected() {
    // RFC 9112 §2.2: servers SHOULD ignore "at least one empty line (CRLF)" before the
    // request-line.  Non-CRLF control bytes are not part of this robustness allowance
    // and must not be silently swallowed.
    EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder());

    ByteBuf buf = Unpooled.buffer();
    buf.writeByte(0x00);   // NUL  — not an empty CRLF line
    buf.writeByte(0x01);   // SOH  — not an empty CRLF line
    buf.writeCharSequence(
            "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n",
            CharsetUtil.US_ASCII);

    channel.writeInbound(buf);
    HttpRequest req = channel.readInbound();

    // Current behaviour: NUL and SOH are in ISO_CONTROL_OR_WHITESPACE, so they are
    // silently skipped; the request decodes successfully and isFailure() == false.
    //
    // RFC-correct behaviour: only empty CRLF lines should be ignored; NUL/SOH must
    // cause a parse error — isFailure() == true.
    assertTrue(
            req.decoderResult().isFailure(),
            "Non-CRLF control bytes before the request-line must not be silently skipped " +
            "(RFC 9112 §2.2 allows only empty CRLF lines)");

    assertFalse(channel.finish());
}

Current behaviour (unfixed): skipControlChars advances past 0x00 and 0x01 because
both are in ISO_CONTROL_OR_WHITESPACE; the request parses normally, isFailure() is
false → test fails.

Expected behaviour after fix: only CRLF empty lines are tolerated; non-CRLF control
bytes produce an error, isFailure() is true → test passes.

Severity

  • CVSS Score: 5.3 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Netty: CRLF Injection via Multipart Filename in Netty HttpPostRequestEncoder

CVE-2026-59921 / GHSA-gcjf-9mgh-3p7g

More information

Details

Security Vulnerability Report: CRLF Injection via Multipart Filename in Netty HttpPostRequestEncoder
1. Vulnerability Summary
Field Value
Product Netty
Version 4.2.12.Final (and all prior versions with codec-http multipart)
Component io.netty.handler.codec.http.multipart.HttpPostRequestEncoder
Vulnerability Type CWE-93: Improper Neutralization of CRLF Sequences / CWE-113: HTTP Response Splitting
Impact MIME Header Injection / Content-Type Spoofing / XSS via Content-Disposition
CVSS 3.1 Score 8.1 (High)
CVSS 3.1 Vector CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N
Attack Vector Network
Attack Complexity Low
Privileges Required Low (attacker must be able to upload files with controlled filenames)
User Interaction None
Scope Unchanged
Confidentiality Impact High
Integrity Impact High
Availability Impact None
2. Affected Components

The following classes in the codec-http module are affected:

  • io.netty.handler.codec.http.multipart.HttpPostRequestEncoder — directly concatenates unvalidated filename/name into Content-Disposition MIME headers (lines 519, 633, 674, 682, 686-688)
  • io.netty.handler.codec.http.multipart.DiskFileUploadsetFilename() only checks null (line 78)
  • io.netty.handler.codec.http.multipart.MemoryFileUploadsetFilename() only checks null (line 60)
  • io.netty.handler.codec.http.multipart.MixedFileUploadsetFilename() delegates without validation (line 62)
3. Vulnerability Description

Netty's HttpPostRequestEncoder constructs multipart HTTP request bodies by directly concatenating user-supplied filenames and field names into Content-Disposition MIME headers without validating or sanitizing CRLF characters (\r\n). Since MIME headers are delimited by CRLF, an attacker who controls the filename can inject arbitrary MIME headers into the multipart body part.

Root Cause

In HttpPostRequestEncoder.java, multiple code paths directly embed fileUpload.getFilename() into header strings:

// Line 674 (attachment mode):
internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": "
    + HttpHeaderValues.ATTACHMENT + "; "
    + HttpHeaderValues.FILENAME + "=\"" + fileUpload.getFilename() + "\"\r\n");
//                                        ^^^^^^^^^^^^^^^^^^^^^^^^ NO VALIDATION

// Lines 686-688 (form-data mode):
internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": " + HttpHeaderValues.FORM_DATA + "; "
    + HttpHeaderValues.NAME + "=\"" + fileUpload.getName() + "\"; "
    + HttpHeaderValues.FILENAME + "=\"" + fileUpload.getFilename() + "\"\r\n");
//                                        ^^^^^^^^^^^^^^^^^^^^^^^^ NO VALIDATION

// Line 519 (attribute name):
internal.addValue(HttpHeaderNames.CONTENT_DISPOSITION + ": " + HttpHeaderValues.FORM_DATA + "; "
    + HttpHeaderValues.NAME + "=\"" + attribute.getName() + "\"\r\n");
//                                    ^^^^^^^^^^^^^^^^^ NO VALIDATION

The setFilename() method in all FileUpload implementations only checks for null:

// DiskFileUpload.java:77-79
public void setFilename(String filename) {
    this.filename = ObjectUtil.checkNotNull(filename, "filename");
    // NO CRLF VALIDATION
}
Comparison with Similar Fixed CVEs

This vulnerability follows the same pattern as:

CVE Component Fix
GHSA-jq43-27x9-3v86 SmtpRequestEncoder — SMTP command injection Added CRLF validation in SmtpUtils.validateSMTPParameters()
GHSA-84h7-rjj3-6jx4 HttpRequestEncoder — CRLF in URI Added HttpUtil.validateRequestLineTokens()

The multipart encoder has no equivalent validation for filenames or field names.

4. Exploitability Prerequisites

This vulnerability is exploitable when:

  1. The application uses Netty's HttpPostRequestEncoder to construct multipart HTTP requests
  2. The filename of an uploaded file is derived from user-controlled input
  3. The application does not perform its own CRLF sanitization on filenames

Common affected patterns:

  • File upload proxies that forward user-supplied filenames
  • API gateways that construct multipart requests from incoming parameters
  • Microservice communication that passes filenames between services
  • Testing/automation frameworks that use Netty HTTP client with user-defined filenames
5. Attack Scenarios
Scenario 1: Content-Type Override via Filename Injection

An attacker uploads a file with a crafted filename to override the Content-Type of the multipart body part, potentially enabling stored XSS:

String maliciousFilename = "photo.jpg\"\r\nContent-Type: text/html\r\n\r\n<script>alert(document.cookie)</script>\r\n--";

DiskFileUpload upload = new DiskFileUpload(
    "avatar", maliciousFilename, "image/jpeg", "binary", UTF_8, fileSize);

Wire format:

--boundary
content-disposition: form-data; name="avatar"; filename="photo.jpg"
Content-Type: text/html                    <-- INJECTED: overrides image/jpeg

<script>alert(document.cookie)</script>    <-- INJECTED: XSS payload
--"
content-type: image/jpeg                   <-- Original (now ignored by many parsers)
...

If the receiving server parses the first Content-Type, the file is treated as HTML instead of JPEG, enabling XSS when the file is served back.

Scenario 2: Arbitrary MIME Header Injection
String filename = "doc.pdf\"\r\nX-Custom-Auth: admin-token-12345\r\nX-Bypass-Check: true";

Injects arbitrary headers into the multipart body part that may be processed by downstream middleware or application logic.

Scenario 3: Multipart Boundary Confusion
String filename = "file.txt\"\r\n\r\nmalicious body content\r\n--boundary\r\nContent-Disposition: form-data; name=\"secret";

By injecting a new boundary delimiter, the attacker can:

  • Terminate the current body part prematurely
  • Start a new body part with a different field name
  • Override form fields processed by the server
6. Proof of Concept
Full Runnable PoC Source Code (MultipartFilenameInjectionPoC.java)
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.*;

import java.io.File;
import java.io.FileWriter;
import java.nio.charset.StandardCharsets;

/**
 * PoC: HTTP Multipart Content-Disposition Header Injection via Filename
 *
 * Demonstrates that HttpPostRequestEncoder does not validate filenames
 * for CRLF characters, allowing injection of arbitrary MIME headers
 * into multipart form data.
 */
public class MultipartFilenameInjectionPoC {

    public static void main(String[] args) throws Exception {
        System.out.println("=== Netty Multipart Filename CRLF Injection PoC ===\n");

        testFilenameInjection();

        System.out.println("\n=== PoC Complete ===");
    }

    static void testFilenameInjection() throws Exception {
        System.out.println("[TEST 1] Filename CRLF Injection in Content-Disposition");
        System.out.println("-------------------------------------------------------");

        // Create a temporary file for upload
        File tempFile = File.createTempFile("test", ".txt");
        tempFile.deleteOnExit();
        try (FileWriter fw = new FileWriter(tempFile)) {
            fw.write("test content");
        }

        // Malicious filename with CRLF to inject Content-Type header
        String maliciousFilename =
            "innocent.txt\"\r\nContent-Type: text/html\r\nX-Injected: true\r\n\r\n" +
            "<script>alert(1)</script>\r\n--";

        HttpRequest request = new DefaultHttpRequest(
            HttpVersion.HTTP_1_1, HttpMethod.POST, "/upload");

        HttpPostRequestEncoder encoder = new HttpPostRequestEncoder(
                new DefaultHttpDataFactory(false), request, true,
                StandardCharsets.UTF_8, HttpPostRequestEncoder.EncoderMode.RFC3986);

        DiskFileUpload fileUpload = new DiskFileUpload(
                "file", maliciousFilename, "application/octet-stream",
                "binary", StandardCharsets.UTF_8, tempFile.length());
        fileUpload.setContent(tempFile);

        encoder.addBodyHttpData(fileUpload);
        encoder.finalizeRequest();

        // Read the encoded multipart body
        StringBuilder body = new StringBuilder();
        while (!encoder.isEndOfInput()) {
            HttpContent chunk = encoder.readChunk(Unpooled.buffer().alloc());
            if (chunk != null) {
                body.append(chunk.content().toString(StandardCharsets.UTF_8));
                chunk.release();
            }
        }
        encoder.cleanFiles();

        String encoded = body.toString();
        System.out.println("Malicious filename: " +
            maliciousFilename.replace("\r", "\\r").replace("\n", "\\n"));
        System.out.println();
        System.out.println("Encoded multipart body:");
        System.out.println("---");
        for (String line : encoded.split("\n", -1)) {
            System.out.println("  " + line.replace("\r", "\\r"));
        }
        System.out.println("---");

        boolean hasInjectedHeader = encoded.contains("X-Injected: true");
        boolean hasInjectedScript = encoded.contains("<script>");
        System.out.println();
        System.out.println("Injected X-Injected header: " + hasInjectedHeader);
        System.out.println("Injected script tag: " + hasInjectedScript);
        System.out.println("VULNERABLE: " +
            ((hasInjectedHeader || hasInjectedScript) ?
                "YES - MIME header injection!" : "NO"));

        tempFile.delete();
    }
}
How to Compile and Run
##### Build Netty (skip tests)
./mvnw install -pl common,buffer,codec,codec-base,codec-http,transport -DskipTests \
  -Dcheckstyle.skip=true -Denforcer.skip=true -Djapicmp.skip=true \
  -Danimal.sniffer.skip=true -Drevapi.skip=true -Dforbiddenapis.skip=true \
  -Dspotbugs.skip=true -q

##### Set classpath
JARS=$(find ~/.m2/repository/io/netty -name "netty-*.jar" -path "*/4.2.12.Final/*" \
  | grep -v sources | grep -v javadoc | tr '\n' ':')

##### Compile and run
javac -cp "$JARS" MultipartFilenameInjectionPoC.java
java -cp "$JARS:." MultipartFilenameInjectionPoC
PoC Execution Output (Verified on Netty 4.2.12.Final)
=== Netty Multipart Filename CRLF Injection PoC ===

[TEST 1] Filename CRLF Injection in Content-Disposition
-------------------------------------------------------
Malicious filename: innocent.txt"\r\nContent-Type: text/html\r\nX-Injected: true\r\n\r\n<script>alert(1)</script>\r\n--

Encoded multipart body:
---
  --88aaade41dbb9f9f\r
  content-disposition: form-data; name="file"; filename="innocent.txt"\r
  Content-Type: text/html\r                          <-- INJECTED
  X-Injected: true\r                                 <-- INJECTED
  \r
  <script>alert(1)</script>\r                        <-- INJECTED XSS
  --"\r
  content-length: 12\r
  content-type: application/octet-stream\r
  content-transfer-encoding: binary\r
  \r
  test content\r
  --88aaade41dbb9f9f--\r
---

Injected X-Injected header: true
Injected script tag: true
VULNERABLE: YES - MIME header injection!

=== PoC Complete ===
7. Impact Analysis
Impact Category Description
Confidentiality HIGH — Injected headers may bypass access controls or leak tokens
Integrity HIGH — Content-Type override enables stored XSS; field name injection allows form data manipulation
Content-Type Spoofing Override application/octet-stream to text/html to serve executable content
Stored XSS Inject <script> tags via Content-Type override when uploaded files are served back
Form Field Override Inject new multipart boundaries to create/override form fields
Downstream Injection Custom MIME headers may affect middleware, CDN, or storage layer behavior
8. Remediation Recommendations
Option 1: Validate in FileUpload.setFilename() (Recommended)
// DiskFileUpload.java / MemoryFileUpload.java / MixedFileUpload.java
public void setFilename(String filename) {
    ObjectUtil.checkNotNull(filename, "filename");
    for (int i = 0; i < filename.length(); i++) {
        char c = filename.charAt(i);
        if (c == '\r' || c == '\n') {
            throw new IllegalArgumentException(
                "filename contains prohibited CRLF character at index " + i);
        }
    }
    this.filename = filename;
}
Option 2: Sanitize in HttpPostRequestEncoder (Defense-in-Depth)

Escape or reject CRLF characters when building Content-Disposition headers:

// HttpPostRequestEncoder.java - add helper method
private static String sanitizeHeaderParam(String value) {
    for (int i = 0; i < value.length(); i++) {
        char c = value.charAt(i);
        if (c == '\r' || c == '\n' || c == '"') {
            throw new ErrorDataEncoderException(
                "Multipart parameter contains prohibited character at index " + i);
        }
    }
    return value;
}

// Then use in Content-Disposition construction:
internal.addValue(... + "=\"" + sanitizeHeaderParam(fileUpload.getFilename()) + "\"\r\n");
Option 3: RFC 2231/5987 Encoding for Filenames

Use proper RFC 2231 encoding for filenames with special characters:

// Encode filename per RFC 5987:
// filename*=UTF-8''encoded%20filename
String encodedFilename = "UTF-8''" + URLEncoder.encode(filename, "UTF-8");
internal.addValue(... + "filename*=" + encodedFilename + "\r\n");
9. References

Severity

  • CVSS Score: 5.7 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:A/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Netty: Security Control Bypass via CORS Short-Circuit Failure

CVE-2026-56746 / GHSA-6cqp-g7gg-8hr5

More information

Details

Summary

Netty's CorsHandler provides a shortCircuit() configuration designed to reject unauthorized cross-origin requests immediately, acting as a security control before requests reach the application. However, due to a logical operator error in the origin evaluation process, this protection can be entirely bypassed. An attacker can bypass the short-circuit mechanism by sending a request with an Origin: null header. This failure forwards unauthorized requests to the backend application, bypassing intended access controls.

Details

In io.netty.handler.codec.http.cors.CorsHandler#channelRead, the short-circuit logic relies on the configuration returned by getForOrigin(origin) to determine if an origin is authorized. If getForOrigin returns a configuration object, the short-circuit check (!(origin == null || config != null)) is bypassed, and the request proceeds to the backend.

The vulnerability is located in the getForOrigin method:

            if (corsConfig.isNullOriginAllowed() || NULL_ORIGIN.equals(requestOrigin)) {
                return corsConfig;
            }

If an attacker sends Origin: null, NULL_ORIGIN.equals(requestOrigin) evaluates to true. The method returns the configuration object regardless of whether isNullOriginAllowed() was configured by the developer. The short-circuit is bypassed.

Impact

Applications relying on CorsHandler's short-circuit feature to prevent unauthorized cross-origin requests from reaching their backend logic are completely exposed. The framework fails to enforce the developer's intended access controls, allowing unauthorized requests to be processed.

Severity

  • CVSS Score: 6.5 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Netty: [HttpContentEncoder] Unbounded Per-Connection Queue Growth via HTTP/1.1 Pipelining Leads to Denial of Service

CVE-2026-59899 / GHSA-q4f6-jm68-57ww

More information

Details

Impact

HttpContentEncoder (the superclass of the production handler HttpContentCompressor) maintains a per-channel ArrayDeque<CharSequence> named acceptEncodingQueue that accumulates attacker-controlled data without any size limit. The queue is filled on the I/O thread for every inbound HTTP request and drained only when the application later writes a non-1xx response. This creates a resource exhaustion vulnerability when an attacker exploits HTTP/1.1 pipelining to flood the connection with requests faster than the application produces responses.

Severity

  • CVSS Score: 6.9 / 10 (Medium)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Netty SPDY SETTINGS frame count materializes unbounded settings map

CVE-2026-55831 / GHSA-6jqx-86gh-f27w

More information

Details

Summary

Netty's SPDY SETTINGS decoder accepts a peer-declared SETTINGS entry count up to the 24-bit frame-length limit and materializes every unique setting ID in DefaultSpdySettingsFrame without an implementation-level count cap. A remote SPDY/3.1 peer can send one syntactically valid roughly 2 MiB SETTINGS frame that creates 262144 map entries, amplifying network input into heap growth and ordered-map insertion work.

Details

Inbound SPDY bytes enter SpdyFrameCodec.decode() and are passed directly to the frame decoder. The decoder reads the peer-controlled flags and 24-bit frame length from the common header, then accepts SETTINGS frames with only length >= 4. For SETTINGS payloads, it reads the peer-controlled numSettings field and validates only that the remaining payload is divisible into 8-byte entries and exactly matches that count. Each accepted entry then supplies an attacker-controlled 24-bit ID and value, and the normal delegate path forwards it into spdySettingsFrame.setValue(). The sink is DefaultSpdySettingsFrame: it backs settings with a TreeMap, checks only that IDs fit the SPDY 24-bit maximum, and inserts a new Setting for each previously unseen ID. There is no count budget between the wire-format count validation and the TreeMap insertion site.

PoC

poc.zip

run with

bash ./poc/run.sh

expected output:

NETTY_SPDY_SETTINGS_COUNT_MAP_TRIGGERED settings_count=262144 wire_bytes=2097164 approx_heap_delta=17692272 first_value=1 last_value=262144

The NETTY_SPDY_SETTINGS_COUNT_MAP_TRIGGERED line means the harness decoded the crafted SETTINGS frame and observed all 262144 peer-selected IDs in the resulting settings map. The wire_bytes=2097164, first_value=1, and last_value=262144 fields distinguish this from a setup failure: they show the exact oversized frame was accepted and fully materialized.

Impact

remote unauthenticated network peer that can speak SPDY/3.1 to a Netty pipeline containing SpdyFrameCodec can trigger resource-exhaustion denial of service. The required guards are satisfied by a complete valid SE

Note

PR body was truncated to here.

@renovate
renovate Bot force-pushed the renovate/netty.version branch from b0b9151 to 49a1781 Compare July 26, 2026 12:22
@renovate renovate Bot changed the title Update netty.version to v4.1.135.Final [SECURITY] Update netty.version [SECURITY] Jul 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants