mirror of
https://github.com/frappe/gunicorn.git
synced 2026-07-07 21:21:30 +08:00
Merge pull request #3597 from benoitc/fix/rfc9110-forbidden-trailer-fields
fix: reject forbidden trailer field-names (RFC 9110 §6.5.1)
This commit is contained in:
commit
9f7f930a81
@ -29,6 +29,18 @@ class InvalidProxyHeader(ParseError):
|
|||||||
PP_V2_SIGNATURE = b"\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"
|
PP_V2_SIGNATURE = b"\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"
|
||||||
|
|
||||||
|
|
||||||
|
# RFC 9110 section 6.5.1: fields forbidden in trailers because they alter
|
||||||
|
# routing, framing, or authentication.
|
||||||
|
RFC9110_6_5_1_FORBIDDEN_TRAILER = frozenset((
|
||||||
|
b"host",
|
||||||
|
b"content-length",
|
||||||
|
b"transfer-encoding",
|
||||||
|
b"trailer",
|
||||||
|
b"authorization",
|
||||||
|
b"te",
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
class PPCommand(IntEnum):
|
class PPCommand(IntEnum):
|
||||||
"""PROXY protocol v2 commands."""
|
"""PROXY protocol v2 commands."""
|
||||||
LOCAL = 0x0
|
LOCAL = 0x0
|
||||||
@ -756,6 +768,14 @@ class PythonProtocol:
|
|||||||
self._on_message_complete()
|
self._on_message_complete()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
# RFC 9110 section 6.5.1: reject fields that must not appear
|
||||||
|
# in trailers.
|
||||||
|
colon = line.find(b':')
|
||||||
|
if colon > 0:
|
||||||
|
name = line[:colon].strip(b' \t').lower()
|
||||||
|
if name in RFC9110_6_5_1_FORBIDDEN_TRAILER:
|
||||||
|
raise InvalidHeaderName(name.decode('latin-1'))
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _is_valid_method(self, method):
|
def _is_valid_method(self, method):
|
||||||
|
|||||||
@ -132,6 +132,18 @@ METHOD_BADCHAR_RE = re.compile("[a-z#]")
|
|||||||
VERSION_RE = re.compile(r"HTTP/(\d)\.(\d)")
|
VERSION_RE = re.compile(r"HTTP/(\d)\.(\d)")
|
||||||
RFC9110_5_5_INVALID_AND_DANGEROUS = re.compile(r"[\0\r\n]")
|
RFC9110_5_5_INVALID_AND_DANGEROUS = re.compile(r"[\0\r\n]")
|
||||||
|
|
||||||
|
# RFC 9110 section 6.5.1: fields forbidden in trailers because they alter
|
||||||
|
# routing, framing, or authentication. Using the uppercased names stored
|
||||||
|
# by parse_headers.
|
||||||
|
RFC9110_6_5_1_FORBIDDEN_TRAILER = frozenset((
|
||||||
|
"HOST",
|
||||||
|
"CONTENT-LENGTH",
|
||||||
|
"TRANSFER-ENCODING",
|
||||||
|
"TRAILER",
|
||||||
|
"AUTHORIZATION",
|
||||||
|
"TE",
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
def _ip_in_allow_list(ip_str, allow_list, networks):
|
def _ip_in_allow_list(ip_str, allow_list, networks):
|
||||||
"""Check if IP address is in the allow list.
|
"""Check if IP address is in the allow list.
|
||||||
@ -235,6 +247,10 @@ class Message:
|
|||||||
# b"\xDF".decode("latin-1").upper().encode("ascii") == b"SS"
|
# b"\xDF".decode("latin-1").upper().encode("ascii") == b"SS"
|
||||||
name = name.upper()
|
name = name.upper()
|
||||||
|
|
||||||
|
# RFC 9110 section 6.5.1
|
||||||
|
if from_trailer and name in RFC9110_6_5_1_FORBIDDEN_TRAILER:
|
||||||
|
raise InvalidHeaderName(name)
|
||||||
|
|
||||||
value = [value.strip(" \t")]
|
value = [value.strip(" \t")]
|
||||||
|
|
||||||
# Consume value continuation lines..
|
# Consume value continuation lines..
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
POST /p HTTP/1.1\r\n
|
||||||
|
Host: example.com\r\n
|
||||||
|
Transfer-Encoding: chunked\r\n
|
||||||
|
\r\n
|
||||||
|
5\r\n
|
||||||
|
hello\r\n
|
||||||
|
0\r\n
|
||||||
|
Content-Length: 99\r\n
|
||||||
|
\r\n
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
#
|
||||||
|
# This file is part of gunicorn released under the MIT license.
|
||||||
|
# See the NOTICE for more information.
|
||||||
|
|
||||||
|
# RFC 9110 section 6.5.1: Content-Length in trailers is a classic
|
||||||
|
# smuggling vector; origin must reject.
|
||||||
|
from gunicorn.http.errors import InvalidHeaderName
|
||||||
|
request = InvalidHeaderName
|
||||||
|
python_only = True
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
POST /p HTTP/1.1\r\n
|
||||||
|
Host: example.com\r\n
|
||||||
|
Transfer-Encoding: chunked\r\n
|
||||||
|
\r\n
|
||||||
|
5\r\n
|
||||||
|
hello\r\n
|
||||||
|
0\r\n
|
||||||
|
Host: evil.example.com\r\n
|
||||||
|
\r\n
|
||||||
11
tests/requests/invalid/rfc9110_trailer_forbidden_host_01.py
Normal file
11
tests/requests/invalid/rfc9110_trailer_forbidden_host_01.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#
|
||||||
|
# This file is part of gunicorn released under the MIT license.
|
||||||
|
# See the NOTICE for more information.
|
||||||
|
|
||||||
|
# RFC 9110 section 6.5.1: certain header fields must not be sent in
|
||||||
|
# trailers because they alter routing or message framing (e.g. Host,
|
||||||
|
# Content-Length, Transfer-Encoding). Accepting them enables smuggling.
|
||||||
|
from gunicorn.http.errors import InvalidHeaderName
|
||||||
|
request = InvalidHeaderName
|
||||||
|
# The C parser (gunicorn_h1c) does not yet enforce this rule.
|
||||||
|
python_only = True
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
POST /p HTTP/1.1\r\n
|
||||||
|
Host: example.com\r\n
|
||||||
|
Transfer-Encoding: chunked\r\n
|
||||||
|
\r\n
|
||||||
|
5\r\n
|
||||||
|
hello\r\n
|
||||||
|
0\r\n
|
||||||
|
Transfer-Encoding: chunked\r\n
|
||||||
|
\r\n
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
#
|
||||||
|
# This file is part of gunicorn released under the MIT license.
|
||||||
|
# See the NOTICE for more information.
|
||||||
|
|
||||||
|
# RFC 9110 section 6.5.1: Transfer-Encoding in trailers alters framing
|
||||||
|
# and must not be accepted.
|
||||||
|
from gunicorn.http.errors import InvalidHeaderName
|
||||||
|
request = InvalidHeaderName
|
||||||
|
python_only = True
|
||||||
Loading…
x
Reference in New Issue
Block a user