Merge pull request #2181 from Sytten/fix/http-desync

Fix/http desync
This commit is contained in:
Benoit Chesneau 2019-11-20 21:28:18 +01:00 committed by GitHub
commit c5be1bae5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 105 additions and 2 deletions

1
THANKS
View File

@ -178,3 +178,4 @@ WooParadog <guohaochuan@gmail.com>
Xie Shi <xieshi@douban.com>
Yue Du <ifduyue@gmail.com>
zakdances <zakdances@gmail.com>
Emile Fugulin <emilefugulin@hotmail.com>

View File

@ -2010,3 +2010,20 @@ class PasteGlobalConf(Setting):
.. versionadded:: 19.7
"""
class StripHeaderSpaces(Setting):
name = "strip_header_spaces"
section = "Server Mechanics"
cli = ["--strip-header-spaces"]
validator = validate_bool
action = "store_true"
default = False
desc = """\
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.
"""

View File

@ -90,7 +90,10 @@ class Message(object):
if curr.find(":") < 0:
raise InvalidHeader(curr.strip())
name, value = curr.split(":", 1)
name = name.rstrip(" \t").upper()
if self.cfg.strip_header_spaces:
name = name.rstrip(" \t").upper()
else:
name = name.upper()
if HEADER_RE.search(name):
raise InvalidHeaderName(name)
@ -128,9 +131,12 @@ class Message(object):
content_length = None
for (name, value) in self.headers:
if name == "CONTENT-LENGTH":
if content_length is not None:
raise InvalidHeader("CONTENT-LENGTH", req=self)
content_length = value
elif name == "TRANSFER-ENCODING":
chunked = value.lower() == "chunked"
if value.lower() == "chunked":
chunked = True
elif name == "SEC-WEBSOCKET-KEY1":
content_length = 8

View File

@ -0,0 +1,4 @@
GET /stuff/here?foo=bar HTTP/1.1\r\n
Content-Length : 3\r\n
\r\n
xyz

View File

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

View File

@ -0,0 +1,5 @@
GET /stuff/here?foo=bar HTTP/1.1\r\n
Content-Length: 3\r\n
Content-Length: 2\r\n
\r\n
xyz

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,4 @@
GET /stuff/here?foo=bar HTTP/1.1\r\n
Content-Length : 3\r\n
\r\n
xyz

View File

@ -0,0 +1,14 @@
from gunicorn.config import Config
cfg = Config()
cfg.set("strip_header_spaces", True)
request = {
"method": "GET",
"uri": uri("/stuff/here?foo=bar"),
"version": (1, 1),
"headers": [
("CONTENT-LENGTH", "3"),
],
"body": b"xyz"
}

View File

@ -0,0 +1,7 @@
GET /stuff/here?foo=bar HTTP/1.1\r\n
Transfer-Encoding: chunked\r\n
Transfer-Encoding: identity\r\n
\r\n
5\r\n
hello\r\n
000\r\n

View File

@ -0,0 +1,14 @@
from gunicorn.config import Config
cfg = Config()
request = {
"method": "GET",
"uri": uri("/stuff/here?foo=bar"),
"version": (1, 1),
"headers": [
('TRANSFER-ENCODING', 'chunked'),
('TRANSFER-ENCODING', 'identity')
],
"body": b"hello"
}

View File

@ -0,0 +1,7 @@
GET /stuff/here?foo=bar HTTP/1.1\r\n
Transfer-Encoding: identity\r\n
Transfer-Encoding: chunked\r\n
\r\n
5\r\n
hello\r\n
000\r\n

View File

@ -0,0 +1,14 @@
from gunicorn.config import Config
cfg = Config()
request = {
"method": "GET",
"uri": uri("/stuff/here?foo=bar"),
"version": (1, 1),
"headers": [
('TRANSFER-ENCODING', 'identity'),
('TRANSFER-ENCODING', 'chunked')
],
"body": b"hello"
}