mirror of
https://github.com/frappe/gunicorn.git
synced 2026-01-14 11:09:11 +08:00
strict: header field validation: stop casefolding
* refusing lowercase and ASCII 0x23 (#) had been partially enforced before * do not casefold by default, HTTP methods are case sensitive
This commit is contained in:
parent
42dd4190ac
commit
b2846783d7
@ -2254,5 +2254,49 @@ class StripHeaderSpaces(Setting):
|
||||
This is known to induce vulnerabilities and is not compliant with the HTTP/1.1 standard.
|
||||
See https://portswigger.net/research/http-desync-attacks-request-smuggling-reborn.
|
||||
|
||||
Use with care and only if necessary.
|
||||
Use with care and only if necessary. May be removed in a future version.
|
||||
|
||||
.. versionadded:: 20.0.1
|
||||
"""
|
||||
|
||||
|
||||
class PermitUnconventionalHTTPMethod(Setting):
|
||||
name = "permit_unconventional_http_method"
|
||||
section = "Server Mechanics"
|
||||
cli = ["--permit-unconventional-http-method"]
|
||||
validator = validate_bool
|
||||
action = "store_true"
|
||||
default = False
|
||||
desc = """\
|
||||
Permit HTTP methods not matching conventions, such as IANA registration guidelines
|
||||
|
||||
This permits request methods of length less than 3 or more than 20,
|
||||
methods with lowercase characters or methods containing the # character.
|
||||
HTTP methods are case sensitive by definition, and merely uppercase by convention.
|
||||
|
||||
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"
|
||||
cli = ["--casefold-http-method"]
|
||||
validator = validate_bool
|
||||
action = "store_true"
|
||||
default = False
|
||||
desc = """\
|
||||
Transform received HTTP methods to uppercase
|
||||
|
||||
HTTP methods are case sensitive by definition, and merely uppercase by convention.
|
||||
|
||||
This option is provided because previous versions of gunicorn defaulted to this behaviour.
|
||||
|
||||
Use with care and only if necessary. May be removed in a future version.
|
||||
|
||||
.. versionadded:: 22.0.0
|
||||
"""
|
||||
|
||||
@ -21,7 +21,10 @@ MAX_REQUEST_LINE = 8190
|
||||
MAX_HEADERS = 32768
|
||||
DEFAULT_MAX_HEADERFIELD_SIZE = 8190
|
||||
|
||||
TOKEN_RE = re.compile(r"[!#$%&'*+\-.\^_`|~0-9a-zA-Z]+")
|
||||
# verbosely on purpose, avoid backslash ambiguity
|
||||
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+)")
|
||||
|
||||
|
||||
@ -331,10 +334,23 @@ class Request(Message):
|
||||
if len(bits) != 3:
|
||||
raise InvalidRequestLine(bytes_to_str(line_bytes))
|
||||
|
||||
# Method
|
||||
if not TOKEN_RE.fullmatch(bits[0]):
|
||||
raise InvalidRequestMethod(bits[0])
|
||||
self.method = bits[0].upper()
|
||||
# Method: RFC9110 Section 9
|
||||
self.method = bits[0]
|
||||
|
||||
# nonstandard restriction, suitable for all IANA registered methods
|
||||
# partially enforced in previous gunicorn versions
|
||||
if not self.cfg.permit_unconventional_http_method:
|
||||
if METHOD_BADCHAR_RE.search(self.method):
|
||||
raise InvalidRequestMethod(self.method)
|
||||
if not 3 <= len(bits[0]) <= 20:
|
||||
raise InvalidRequestMethod(self.method)
|
||||
# standard restriction: RFC9110 token
|
||||
if not TOKEN_RE.fullmatch(self.method):
|
||||
raise InvalidRequestMethod(self.method)
|
||||
# nonstandard and dangerous
|
||||
# methods are merely uppercase by convention, no case-insensitive treatment is intended
|
||||
if self.cfg.casefold_http_method:
|
||||
self.method = self.method.upper()
|
||||
|
||||
# URI
|
||||
self.uri = bits[1]
|
||||
|
||||
2
tests/requests/invalid/003b.http
Normal file
2
tests/requests/invalid/003b.http
Normal file
@ -0,0 +1,2 @@
|
||||
bla:rgh /foo HTTP/1.1\r\n
|
||||
\r\n
|
||||
2
tests/requests/invalid/003b.py
Normal file
2
tests/requests/invalid/003b.py
Normal file
@ -0,0 +1,2 @@
|
||||
from gunicorn.http.errors import InvalidRequestMethod
|
||||
request = InvalidRequestMethod
|
||||
2
tests/requests/invalid/003c.http
Normal file
2
tests/requests/invalid/003c.http
Normal file
@ -0,0 +1,2 @@
|
||||
-bl /foo HTTP/1.1\r\n
|
||||
\r\n
|
||||
2
tests/requests/invalid/003c.py
Normal file
2
tests/requests/invalid/003c.py
Normal file
@ -0,0 +1,2 @@
|
||||
from gunicorn.http.errors import InvalidRequestMethod
|
||||
request = InvalidRequestMethod
|
||||
@ -1,2 +1,2 @@
|
||||
-blargh /foo HTTP/1.1\r\n
|
||||
\r\n
|
||||
-BLARGH /foo HTTP/1.1\r\n
|
||||
\r\n
|
||||
|
||||
2
tests/requests/valid/031compat.http
Normal file
2
tests/requests/valid/031compat.http
Normal file
@ -0,0 +1,2 @@
|
||||
-blargh /foo HTTP/1.1\r\n
|
||||
\r\n
|
||||
13
tests/requests/valid/031compat.py
Normal file
13
tests/requests/valid/031compat.py
Normal file
@ -0,0 +1,13 @@
|
||||
from gunicorn.config import Config
|
||||
|
||||
cfg = Config()
|
||||
cfg.set("permit_unconventional_http_method", True)
|
||||
cfg.set("casefold_http_method", True)
|
||||
|
||||
request = {
|
||||
"method": "-BLARGH",
|
||||
"uri": uri("/foo"),
|
||||
"version": (1, 1),
|
||||
"headers": [],
|
||||
"body": b""
|
||||
}
|
||||
2
tests/requests/valid/031compat2.http
Normal file
2
tests/requests/valid/031compat2.http
Normal file
@ -0,0 +1,2 @@
|
||||
-blargh /foo HTTP/1.1\r\n
|
||||
\r\n
|
||||
12
tests/requests/valid/031compat2.py
Normal file
12
tests/requests/valid/031compat2.py
Normal file
@ -0,0 +1,12 @@
|
||||
from gunicorn.config import Config
|
||||
|
||||
cfg = Config()
|
||||
cfg.set("permit_unconventional_http_method", True)
|
||||
|
||||
request = {
|
||||
"method": "-blargh",
|
||||
"uri": uri("/foo"),
|
||||
"version": (1, 1),
|
||||
"headers": [],
|
||||
"body": b""
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user