strict HTTP version validation

Note: This is unrelated to a reverse proxy potentially talking HTTP/3 to clients.
This is about the HTTP protocol version spoken to Gunicorn, which is HTTP/1.0 or HTTP/1.1.

Little legitimate need for processing HTTP 1 requests with ambiguous version numbers.
Broadly refuse.

Co-authored-by: Ben Kallus <benjamin.p.kallus.gr@dartmouth.edu>
This commit is contained in:
Paul J. Dorn 2023-12-07 09:56:49 +01:00
parent f5501111a2
commit 7ebe442d08
8 changed files with 43 additions and 1 deletions

View File

@ -2282,6 +2282,26 @@ class PermitUnconventionalHTTPMethod(Setting):
"""
class PermitUnconventionalHTTPVersion(Setting):
name = "permit_unconventional_http_version"
section = "Server Mechanics"
cli = ["--permit-unconventional-http-version"]
validator = validate_bool
action = "store_true"
default = False
desc = """\
Permit HTTP version not matching conventions of 2023
This disables the refusal of likely malformed request lines.
It is unusual to specify HTTP 1 versions other than 1.0 and 1.1.
This option is provided to diagnose backwards-incompatible changes.
Use with care and only if necessary. May be removed in a future version.
.. versionadded:: 22.0.0
"""
class CasefoldHTTPMethod(Setting):
name = "casefold_http_method"
section = "Server Mechanics"

View File

@ -26,7 +26,8 @@ DEFAULT_MAX_HEADERFIELD_SIZE = 8190
RFC9110_5_6_2_TOKEN_SPECIALS = r"!#$%&'*+-.^_`|~"
TOKEN_RE = re.compile(r"[%s0-9a-zA-Z]+" % (re.escape(RFC9110_5_6_2_TOKEN_SPECIALS)))
METHOD_BADCHAR_RE = re.compile("[a-z#]")
VERSION_RE = re.compile(r"HTTP/(\d+)\.(\d+)")
# usually 1.0 or 1.1 - RFC9112 permits restricting to single-digit versions
VERSION_RE = re.compile(r"HTTP/(\d)\.(\d)")
class Message(object):
@ -438,6 +439,10 @@ class Request(Message):
if match is None:
raise InvalidHTTPVersion(bits[2])
self.version = (int(match.group(1)), int(match.group(2)))
if not (1, 0) <= self.version < (2, 0):
# if ever relaxing this, carefully review Content-Encoding processing
if not self.cfg.permit_unconventional_http_version:
raise InvalidHTTPVersion(self.version)
def set_body_reader(self):
super().set_body_reader()

View File

@ -0,0 +1,4 @@
GET /the/future HTTP/1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111.1\r\n
Content-Length: 7\r\n
\r\n
Old Man

View File

@ -0,0 +1,5 @@
from gunicorn.config import Config
from gunicorn.http.errors import InvalidHTTPVersion
cfg = Config()
request = InvalidHTTPVersion

View File

@ -0,0 +1,2 @@
GET /foo HTTP/0.99\r\n
\r\n

View File

@ -0,0 +1,2 @@
from gunicorn.http.errors import InvalidHTTPVersion
request = InvalidHTTPVersion

View File

@ -0,0 +1,2 @@
GET /foo HTTP/2.0\r\n
\r\n

View File

@ -0,0 +1,2 @@
from gunicorn.http.errors import InvalidHTTPVersion
request = InvalidHTTPVersion