Skip to content

HTTP Request Smuggling: TE.TE desync and obs-fold parsing in WEBrick #204

@rodtvs

Description

@rodtvs

Summary

WEBrick contains two independent weaknesses in HTTP/1.1 request parsing
that create HTTP Request Smuggling conditions when deployed behind a
reverse proxy (Nginx, HAProxy, AWS ALB) with connection pooling.

Both bugs are confirmed directly in WEBrick via raw TCP no proxy
required to reproduce the parsing behavior.

This report was originally submitted via HackerOne with PoC and redirected here by @hsbt.


Bug 1 TE.TE Desync: Multiple Transfer-Encoding Headers

File: lib/webrick/httputils.rb:155-159 / lib/webrick/httprequest.rb:550-557
CWE: CWE-444

When a request contains multiple Transfer-Encoding headers, WEBrick
joins their values using ", " via SplitHeader#join, producing
"chunked, identity". This fails the regex /\Achunked\z/io, causing
WEBrick to return 501 Not Implemented instead of the RFC-required
400 Bad Request.

The unread body bytes remain in the socket buffer and are processed
as the next request under a keep-alive connection.

Minimal reproduction (direct TCP, no proxy):

import socket

def raw(payload):
    s = socket.socket()
    s.connect(("127.0.0.1", 18080))
    s.sendall(payload)
    r = b""
    s.settimeout(2)
    try:
        while True:
            c = s.recv(4096)
            if not c: break
            r += c
    except: pass
    s.close()
    return r.decode(errors="replace")

# Single TE header -> 200 OK
raw(b"POST /submit HTTP/1.1\r\nHost: localhost\r\n"
    b"Transfer-Encoding: chunked\r\nConnection: close\r\n\r\n"
    b"5\r\nhello\r\n0\r\n\r\n")

# Two TE headers -> 501 Not Implemented
raw(b"POST /submit HTTP/1.1\r\nHost: localhost\r\n"
    b"Transfer-Encoding: chunked\r\n"
    b"Transfer-Encoding: identity\r\n"
    b"Connection: close\r\n\r\n"
    b"5\r\nhello\r\n0\r\n\r\n")

Observed output (WEBrick 1.9.2 / Ruby 4.0.1):

Single TE:  HTTP/1.1 200 OK
Two TE:     HTTP/1.1 501 Not Implemented
            body: "Transfer-Encoding: chunked, identity."

The 501 response body exposes the joined value, confirming
SplitHeader#join in httputils.rb:156 as the root cause.


Bug 2 - Obs-fold Desync: Obsolete Line Folding in Transfer-Encoding

File: lib/webrick/httputils.rb:181-188
CWE: CWE-444

WEBrick's parse_header merges obs-fold continuation lines into the
previous header value. A request containing:

Transfer-Encoding: chunked\r\n
\tidentity\r\n

Is parsed by WEBrick as Transfer-Encoding: chunked identity.

Frontend proxies that do not support obs-fold (HAProxy < 2.0.6, standard
Nginx) see only Transfer-Encoding: chunked and process the body
accordingly - creating a parsing asymmetry that is the foundation of
TE.TE smuggling. This is the same mechanism documented in CVE-2019-18277.

RFC 7230 §3.2.6 states servers MUST either reject obs-fold in request
headers or replace it with SP octets. WEBrick applies the second option
but does not validate the resulting TE value, leaving the desync surface open.


Affected Versions

Confirmed on WEBrick 1.9.2 / Ruby 4.0.1. Likely affects earlier versions.


Recommended Fixes

Bug 1 - Reject multiple Transfer-Encoding headers

# lib/webrick/httprequest.rb - inside read_header
if (te = @header['transfer-encoding']) && te.length > 1
  raise HTTPStatus::BadRequest, "multiple transfer-encoding headers"
end

Bug 2 - Reject obs-fold in request headers

# lib/webrick/httputils.rb:181-188
when /^[ \t]+([^\r\n\0]*?)\r\n/om
  raise HTTPStatus::BadRequest,
    "obs-fold in request header is not allowed (RFC 7230 §3.2.6)"

References

Full technical details and end-to-end PoC available upon request.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions