From d1e0df9c0d7e2abfeb1113996c1fe24235e4d79b Mon Sep 17 00:00:00 2001 From: Jeppe Weikop Date: Sun, 24 Nov 2024 01:50:52 +0100 Subject: [PATCH 1/2] fix problematic parsing leniency in parsing chunk extensions --- gunicorn/http/body.py | 6 +++++- gunicorn/http/errors.py | 8 ++++++++ tests/requests/invalid/chunked_14.http | 7 +++++++ tests/requests/invalid/chunked_14.py | 2 ++ 4 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 tests/requests/invalid/chunked_14.http create mode 100644 tests/requests/invalid/chunked_14.py diff --git a/gunicorn/http/body.py b/gunicorn/http/body.py index d7ee29e78..4ba0a4f20 100644 --- a/gunicorn/http/body.py +++ b/gunicorn/http/body.py @@ -6,7 +6,7 @@ import sys from gunicorn.http.errors import (NoMoreData, ChunkMissingTerminator, - InvalidChunkSize) + InvalidChunkSize, InvalidChunkExtension) class ChunkedReader: @@ -91,6 +91,10 @@ def parse_chunk_size(self, unreader, data=None): chunk_size, *chunk_ext = line.split(b";", 1) if chunk_ext: chunk_size = chunk_size.rstrip(b" \t") + # Security: Don't newlines in chunk extension + # This can cause request smuggling issues with some proxies + if b"\n" in chunk_ext[0]: + raise InvalidChunkExtension(chunk_ext[0]) if any(n not in b"0123456789abcdefABCDEF" for n in chunk_size): raise InvalidChunkSize(chunk_size) if len(chunk_size) == 0: diff --git a/gunicorn/http/errors.py b/gunicorn/http/errors.py index bcb970072..8ed1d0b75 100644 --- a/gunicorn/http/errors.py +++ b/gunicorn/http/errors.py @@ -97,6 +97,14 @@ def __str__(self): return "Invalid chunk size: %r" % self.data +class InvalidChunkExtension(IOError): + def __init__(self, data): + self.data = data + + def __str__(self): + return "Invalid chunk extension: %r" % self.data + + class ChunkMissingTerminator(IOError): def __init__(self, term): self.term = term diff --git a/tests/requests/invalid/chunked_14.http b/tests/requests/invalid/chunked_14.http new file mode 100644 index 000000000..e51e15d95 --- /dev/null +++ b/tests/requests/invalid/chunked_14.http @@ -0,0 +1,7 @@ +POST /chunked_newline_in_chunk_ext HTTP/1.1\r\n +Transfer-Encoding: chunked\r\n +\r\n +5;foo\nbar\r\n +hello\r\n +0\r\n +\r\n diff --git a/tests/requests/invalid/chunked_14.py b/tests/requests/invalid/chunked_14.py new file mode 100644 index 000000000..14ef63b43 --- /dev/null +++ b/tests/requests/invalid/chunked_14.py @@ -0,0 +1,2 @@ +from gunicorn.http.errors import InvalidChunkExtension +request = InvalidChunkExtension From cc839ceb746bb4340b8e05916f931ed13ac1852e Mon Sep 17 00:00:00 2001 From: Jeppe Weikop Date: Sun, 24 Nov 2024 22:17:33 +0100 Subject: [PATCH 2/2] also disallow \r --- gunicorn/http/body.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gunicorn/http/body.py b/gunicorn/http/body.py index 4ba0a4f20..01ad4637d 100644 --- a/gunicorn/http/body.py +++ b/gunicorn/http/body.py @@ -91,9 +91,9 @@ def parse_chunk_size(self, unreader, data=None): chunk_size, *chunk_ext = line.split(b";", 1) if chunk_ext: chunk_size = chunk_size.rstrip(b" \t") - # Security: Don't newlines in chunk extension + # Security: Don't allow CRs and LFs in chunk extensions # This can cause request smuggling issues with some proxies - if b"\n" in chunk_ext[0]: + if any(c in chunk_ext[0] for c in (b"\n", b"\r")): raise InvalidChunkExtension(chunk_ext[0]) if any(n not in b"0123456789abcdefABCDEF" for n in chunk_size): raise InvalidChunkSize(chunk_size)