GHSA-C4C3-7FPV-J4Q5
Vulnerability from github – Published: 2026-09-08 18:16 – Updated: 2026-09-08 18:16Summary
A fragmented TLS ClientHello whose handshake header spans multiple records makes Netty silently fall back to the default SslContext; where per-SNI selection is the sole mTLS gate, an unauthenticated attacker can bypass the route's mTLS requirement.
Details
In io.netty.handler.ssl.SslClientHelloHandler#decode the guard that should wait for the 4-byte handshake header checks the wrong offset - it ignores the 5-byte record header that precedes it - and therefore never fires:
if (handshakeLength == -1) {
if (readerIndex + 4 > endOffset) {
// Need more data to read HandshakeType and handshakeLength (4 bytes)
return;
}
When the first record's payload is < 4 bytes, handshakeLength = in.getUnsignedMedium(readerIndex + SslUtils.SSL_RECORD_HEADER_LENGTH + 1); leads to IndexOutOfBoundsException. That is caught by the generic catch (Exception) block, which calls select(ctx, null) - this is the default SslContext. Fallback to default on parse failure is a problem when per-SNI selection is the sole mTLS gate.
Impact
SNI routing bypass. Escalates to an unauthenticated mTLS bypass only when: - mTLS is enforced solely via per-SNI SslContext (clientAuth=REQUIRE) - the default/fallback SslContext is permissive (clientAuth=NONE/OPTIONAL) - no secondary peer-certificate verification exists at the application layer.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.2.16.Final"
},
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-handler"
},
"ranges": [
{
"events": [
{
"introduced": "4.2.0.Final"
},
{
"fixed": "4.2.17.Final"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.1.136.Final"
},
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-handler"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.1.137.Final"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-75595"
],
"database_specific": {
"cwe_ids": [
"CWE-475",
"CWE-754"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-08T18:16:14Z",
"nvd_published_at": "2026-08-19T21:17:37Z",
"severity": "CRITICAL"
},
"details": "### Summary\nA fragmented TLS ClientHello whose handshake header spans multiple records makes Netty silently fall back to the default SslContext; where per-SNI selection is the sole mTLS gate, an unauthenticated attacker can bypass the route\u0027s mTLS requirement.\n\n### Details\nIn `io.netty.handler.ssl.SslClientHelloHandler#decode` the guard that should wait for the 4-byte handshake header checks the wrong offset - it ignores the 5-byte record header that precedes it - and therefore never fires:\n\n```java\nif (handshakeLength == -1) {\n if (readerIndex + 4 \u003e endOffset) {\n // Need more data to read HandshakeType and handshakeLength (4 bytes)\n return;\n }\n```\n\nWhen the first record\u0027s payload is \u003c 4 bytes, `handshakeLength = in.getUnsignedMedium(readerIndex + SslUtils.SSL_RECORD_HEADER_LENGTH + 1);` leads to `IndexOutOfBoundsException `. That is caught by the generic `catch (Exception)` block, which calls `select(ctx, null)` - this is the default `SslContext`. Fallback to default on parse failure is a problem when per-SNI selection is the sole mTLS gate.\n\n### Impact\nSNI routing bypass. Escalates to an unauthenticated mTLS bypass only when:\n- mTLS is enforced solely via per-SNI SslContext (clientAuth=REQUIRE)\n- the default/fallback SslContext is permissive (clientAuth=NONE/OPTIONAL)\n- no secondary peer-certificate verification exists at the application layer.",
"id": "GHSA-c4c3-7fpv-j4q5",
"modified": "2026-09-08T18:16:14Z",
"published": "2026-09-08T18:16:14Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/netty/netty/security/advisories/GHSA-c4c3-7fpv-j4q5"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-75595"
},
{
"type": "WEB",
"url": "https://github.com/netty/netty/pull/17213"
},
{
"type": "WEB",
"url": "https://github.com/netty/netty/pull/17217"
},
{
"type": "WEB",
"url": "https://github.com/netty/netty/commit/1b5abc6443b63726c72cdd285af2feb7ddbb8ff7"
},
{
"type": "WEB",
"url": "https://github.com/netty/netty/commit/9e0519239108a69b7e9bbc5e9182ee139a0d7961"
},
{
"type": "PACKAGE",
"url": "https://github.com/netty/netty"
},
{
"type": "WEB",
"url": "https://github.com/netty/netty/releases/tag/netty-4.1.137.Final"
},
{
"type": "WEB",
"url": "https://github.com/netty/netty/releases/tag/netty-4.2.17.Final"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Netty: SNI Routing Bypass via Fragmented TLS ClientHello Causing Fallback to Default SslContext"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.