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.
|
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.
|
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
|
MAX_HEADERS = 32768
|
||||||
DEFAULT_MAX_HEADERFIELD_SIZE = 8190
|
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+)")
|
VERSION_RE = re.compile(r"HTTP/(\d+)\.(\d+)")
|
||||||
|
|
||||||
|
|
||||||
@ -331,10 +334,23 @@ class Request(Message):
|
|||||||
if len(bits) != 3:
|
if len(bits) != 3:
|
||||||
raise InvalidRequestLine(bytes_to_str(line_bytes))
|
raise InvalidRequestLine(bytes_to_str(line_bytes))
|
||||||
|
|
||||||
# Method
|
# Method: RFC9110 Section 9
|
||||||
if not TOKEN_RE.fullmatch(bits[0]):
|
self.method = bits[0]
|
||||||
raise InvalidRequestMethod(bits[0])
|
|
||||||
self.method = bits[0].upper()
|
# 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
|
# URI
|
||||||
self.uri = bits[1]
|
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
|
-BLARGH /foo HTTP/1.1\r\n
|
||||||
\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