ruby / webrick

HTTP server toolkit

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Request Smuggling in WEBrick via bad chunk-size parsing

kenballus opened this issue · comments

When WEBrick receives a request containing an invalid chunk size, it is interpreted as its longest valid prefix. Thus, chunk sizes that begin with 0x are treated as equivalent to 0.

To see why this is a security problem, consider the following payload:

POST / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n0x3a\r\n\r\nGET /evil HTTP/1.1\r\nContent-Length: 23\r\nE: vil\r\nEvil: \r\n\r\n0\r\n\r\nGET / HTTP/1.1\r\n\r\n

WEBrick sees it as a POST request for /, followed by a GET for /evil:

POST / HTTP/1.1\r\n
Transfer-Encoding: chunked\r\n
\r\n
0x3a\r\n
\r\n
GET /evil HTTP/1.1\r\n
Content-Length: 23\r\n
E: vil\r\n
Evil: \r\n
\r\n
0\r\n\r\nGET / HTTP/1.1\r\n\r\n

Unfortunately, some HTTP servers ignore 0x prefixes in chunk sizes due to bad parsing logic. Thus, many servers see a POST request for / and a GET request for /:

POST / HTTP/1.1\r\n
Transfer-Encoding: chunked\r\n
\r\n
0x3a\r\n
\r\nGET /evil HTTP/1.1\r\nContent-Length: 23\r\nE: vil\r\nEvil: \r\n\r\n
0\r\n
\r\n
GET / HTTP/1.1\r\n
\r\n

This discrepancy is exploitable to bypass request filtering rules implemented in reverse proxies that ignore 0x prefixes.

Reviewing RFC 9112 section 7.1 (https://datatracker.ietf.org/doc/html/rfc9112#section-7.1), I think the issue is webrick is interpreting x3a as a chunk extension when it is not a valid chunk extension. Maybe this will work (needs a test added):

diff --git a/lib/webrick/httprequest.rb b/lib/webrick/httprequest.rb
index 7a1686b..23d9b05 100644
--- a/lib/webrick/httprequest.rb
+++ b/lib/webrick/httprequest.rb
@@ -542,7 +542,7 @@ module WEBrick

     def read_chunk_size(socket)
       line = read_line(socket)
-      if /^([0-9a-fA-F]+)(?:;(\S+))?/ =~ line
+      if /\A([0-9a-fA-F]+)(?:;(\S+=\S+))?\r\n\z/ =~ line
         chunk_size = $1.hex
         chunk_ext = $2
         [ chunk_size, chunk_ext ]

The value of a chunk-ext is optional:

chunk-ext = *( BWS ";" BWS chunk-ext-name [ BWS "=" BWS chunk-ext-val ] )

So I think the patch should maybe be this:

diff --git a/lib/webrick/httprequest.rb b/lib/webrick/httprequest.rb
index 7a1686b..23d9b05 100644
--- a/lib/webrick/httprequest.rb
+++ b/lib/webrick/httprequest.rb
@@ -542,7 +542,7 @@ module WEBrick

     def read_chunk_size(socket)
       line = read_line(socket)
-      if /^([0-9a-fA-F]+)(?:;(\S+))?/ =~ line
+      if /\A([0-9a-fA-F]+)(?:;(\S+(?:=\S+)?))?\r\n\z/ =~ line
         chunk_size = $1.hex
         chunk_ext = $2
         [ chunk_size, chunk_ext ]

Also, I think the primary issue was with the missing \z from the end of the pattern, since WEBrick wouldn't see the x3a as a chunk-ext without there also being a ;.

Agreed. I'm not sure when I'll have time to work on a test for this, but your patch looks good.

@kenballus I submitted a pull request that uses your fix and adds a test: #125