Merge pull request #3253 from pajod/patch-rfc9110-section5.5

Refuse requests with invalid and dangerous CR/LF/NUL in header field value, as demanded by rfc9110 section 5.5
This commit is contained in:
Benoit Chesneau 2024-08-06 22:25:12 +02:00 committed by GitHub
commit 9a96e75808
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 189 additions and 12 deletions

View File

@ -210,7 +210,7 @@ H protocol
s status
B response length
b response length or ``'-'`` (CLF format)
f referrer
f referer
a user agent
T request time in seconds
M request time in milliseconds
@ -347,7 +347,7 @@ Format: https://docs.python.org/3/library/logging.config.html#logging.config.jso
**Command line:** ``--log-syslog-to SYSLOG_ADDR``
**Default:** ``'unix:///var/run/syslog'``
**Default:** ``'udp://localhost:514'``
Address to send syslog messages.
@ -527,7 +527,7 @@ SSL certificate file
SSL version to use (see stdlib ssl module's).
.. deprecated:: 20.2
.. deprecated:: 21.0
The option is deprecated and it is currently ignored. Use :ref:`ssl-context` instead.
============= ============
@ -569,7 +569,7 @@ Whether client certificate is required (see stdlib ssl module's)
=========== ===========================
--cert-reqs Description
=========== ===========================
`0` no client verification
`0` no client veirifcation
`1` ssl.CERT_OPTIONAL
`2` ssl.CERT_REQUIRED
=========== ===========================
@ -982,7 +982,7 @@ Following example shows a configuration file that sets the minimum TLS version t
context.minimum_version = ssl.TLSVersion.TLSv1_3
return context
.. versionadded:: 20.2
.. versionadded:: 21.0
Server Mechanics
----------------
@ -1390,7 +1390,7 @@ Set a PasteDeploy global config variable in ``key=value`` form.
The option can be specified multiple times.
The variables are passed to the the PasteDeploy entrypoint. Example::
The variables are passed to the PasteDeploy entrypoint. Example::
$ gunicorn -b 127.0.0.1:8000 --paste development.ini --paste-global FOO=1 --paste-global BAR=2
@ -1410,7 +1410,125 @@ Strip spaces present between the header name and the the ``:``.
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. Deprecated; scheduled for removal in 25.0.0
.. versionadded:: 20.0.1
.. _permit-unconventional-http-method:
``permit_unconventional_http_method``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Command line:** ``--permit-unconventional-http-method``
**Default:** ``False``
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.
If unset, Gunicorn will apply nonstandard restrictions and cause 400 response status
in cases where otherwise 501 status is expected. While this option does modify that
behaviour, it should not be depended upon to guarantee standards-compliant behaviour.
Rather, it is provided temporarily, to assist in diagnosing backwards-incompatible
changes around the incomplete application of those restrictions.
Use with care and only if necessary. Temporary; scheduled for removal in 24.0.0
.. versionadded:: 22.0.0
.. _permit-unconventional-http-version:
``permit_unconventional_http_version``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Command line:** ``--permit-unconventional-http-version``
**Default:** ``False``
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. Temporary; the precise effect of this option may
change in a future version, or it may be removed altogether.
.. versionadded:: 22.0.0
.. _casefold-http-method:
``casefold_http_method``
~~~~~~~~~~~~~~~~~~~~~~~~
**Command line:** ``--casefold-http-method``
**Default:** ``False``
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. Deprecated; scheduled for removal in 24.0.0
.. versionadded:: 22.0.0
.. _header-map:
``header_map``
~~~~~~~~~~~~~~
**Command line:** ``--header-map``
**Default:** ``'drop'``
Configure how header field names are mapped into environ
Headers containing underscores are permitted by RFC9110,
but gunicorn joining headers of different names into
the same environment variable will dangerously confuse applications as to which is which.
The safe default ``drop`` is to silently drop headers that cannot be unambiguously mapped.
The value ``refuse`` will return an error if a request contains *any* such header.
The value ``dangerous`` matches the previous, not advisabble, behaviour of mapping different
header field names into the same environ name.
Use with care and only if necessary and after considering if your problem could
instead be solved by specifically renaming or rewriting only the intended headers
on a proxy in front of Gunicorn.
.. versionadded:: 22.0.0
.. _tolerate-dangerous-framing:
``tolerate_dangerous_framing``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Command line:** ``--tolerate-dangerous-framing``
**Default:** ``False``
Process requests with both Transfer-Encoding and Content-Length
This is known to induce vulnerabilities, but not strictly forbidden by RFC9112.
In any case, the connection is closed after the malformed request,
as it is unclear if and at which boundary additional requests start.
Use with care and only if necessary.
Temporary; will be changed or removed in a future version.
.. versionadded:: 22.0.0
.. versionchanged: 22.1.0
The newly added rejection of invalid and dangerous characters CR, LF and NUL in
header field values is also controlled with this setting. rfc9110 permits both
rejecting and SP-replacing. With this option set, Gunicorn passes the field value
unchanged. With this option unset, Gunicorn rejects the request.
Server Socket
-------------

View File

@ -2254,7 +2254,7 @@ 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. May be removed in a future version.
Use with care and only if necessary. Deprecated; scheduled for removal in 25.0.0
.. versionadded:: 20.0.1
"""
@ -2274,9 +2274,13 @@ class PermitUnconventionalHTTPMethod(Setting):
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.
If unset, Gunicorn will apply nonstandard restrictions and cause 400 response status
in cases where otherwise 501 status is expected. While this option does modify that
behaviour, it should not be depended upon to guarantee standards-compliant behaviour.
Rather, it is provided temporarily, to assist in diagnosing backwards-incompatible
changes around the incomplete application of those restrictions.
Use with care and only if necessary. May be removed in a future version.
Use with care and only if necessary. Temporary; scheduled for removal in 24.0.0
.. versionadded:: 22.0.0
"""
@ -2296,7 +2300,8 @@ class PermitUnconventionalHTTPVersion(Setting):
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.
Use with care and only if necessary. Temporary; the precise effect of this option may
change in a future version, or it may be removed altogether.
.. versionadded:: 22.0.0
"""
@ -2316,7 +2321,7 @@ class CasefoldHTTPMethod(Setting):
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.
Use with care and only if necessary. Deprecated; scheduled for removal in 24.0.0
.. versionadded:: 22.0.0
"""
@ -2378,7 +2383,16 @@ class TolerateDangerousFraming(Setting):
This is known to induce vulnerabilities, but not strictly forbidden by RFC9112.
Use with care and only if necessary. May be removed in a future version.
In any case, the connection is closed after the malformed request,
as it is unclear if and at which boundary additional requests start.
Use with care and only if necessary.
Temporary; will be changed or removed in a future version.
.. versionadded:: 22.0.0
.. versionchanged: 22.1.0
The newly added rejection of invalid and dangerous characters CR, LF and NUL in
header field values is also controlled with this setting. rfc9110 permits both
rejecting and SP-replacing. With this option set, Gunicorn passes the field value
unchanged. With this option unset, Gunicorn rejects the request.
"""

View File

@ -28,6 +28,7 @@ TOKEN_RE = re.compile(r"[%s0-9a-zA-Z]+" % (re.escape(RFC9110_5_6_2_TOKEN_SPECIAL
METHOD_BADCHAR_RE = re.compile("[a-z#]")
# usually 1.0 or 1.1 - RFC9112 permits restricting to single-digit versions
VERSION_RE = re.compile(r"HTTP/(\d)\.(\d)")
RFC9110_5_5_INVALID_AND_DANGEROUS = re.compile(r"[\0\r\n]")
class Message(object):
@ -121,6 +122,12 @@ class Message(object):
value.append(curr.strip("\t "))
value = " ".join(value)
if RFC9110_5_5_INVALID_AND_DANGEROUS.search(value):
if not self.cfg.tolerate_dangerous_framing:
raise InvalidHeader(name)
# value = RFC9110_5_5_INVALID_AND_DANGEROUS.sub(" ", value)
self.force_close()
if header_length > self.limit_request_field_size > 0:
raise LimitRequestHeaders("limit request headers fields size")

View File

@ -0,0 +1,7 @@
GET / HTTP/1.1\r\n
Host: x\r\n
Newline: a\n
Content-Length: 26\r\n
GET / HTTP/1.1\n
Host: x\r\n
\r\n

View File

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

View File

@ -0,0 +1,8 @@
GET / HTTP/1.1\r\n
Host: x\r\n
Newline: a\n
Content-Length: 26\r\n
X-Forwarded-By: broken-proxy\r\n\r\n
GET / HTTP/1.1\n
Host: x\r\n
\r\n

View File

@ -0,0 +1,18 @@
from gunicorn.config import Config
cfg = Config()
cfg.set("tolerate_dangerous_framing", True)
req1 = {
"method": "GET",
"uri": uri("/"),
"version": (1, 1),
"headers": [
("HOST", "x"),
("NEWLINE", "a\nContent-Length: 26"),
("X-FORWARDED-BY", "broken-proxy"),
],
"body": b""
}
request = [req1]