Skip to content

Commit 0b4c939

Browse files
authored
Merge pull request #3113 from pajod/patch-security
Fix numerous message parsing issues (v2)
2 parents 26aba9e + e710393 commit 0b4c939

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+774
-95
lines changed

SECURITY.md

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Security Policy
2+
3+
## Reporting a Vulnerability
4+
5+
**Please note that public Github issues are open for everyone to see!**
6+
7+
If you believe you are found a problem in Gunicorn software, examples or documentation, we encourage you to send your report privately via email, or via Github using the *Report a vulnerability* button in the [Security](https://github.com/benoitc/gunicorn/security) section.
8+
9+
## Supported Releases
10+
11+
At this time, **only the latest release** receives any security attention whatsoever.
12+
13+
| Version | Status |
14+
| ------- | ------------------ |
15+
| latest release | :white_check_mark: |
16+
| 21.2.0 | :x: |
17+
| 20.0.0 | :x: |
18+
| < 20.0 | :x: |
19+
20+
## Python Versions
21+
22+
Gunicorn runs on Python 3.7+, we *highly recommend* the latest release of a [supported series](https://devguide.python.org/version/) and will not prioritize issues exclusively affecting in EoL environments.

docs/source/2023-news.rst

+22
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,28 @@
22
Changelog - 2023
33
================
44

5+
22.0.0 - TBDTBDTBD
6+
==================
7+
8+
- fix numerous security vulnerabilites in HTTP parser (closing some request smuggling vectors)
9+
- parsing additional requests is no longer attempted past unsupported request framing
10+
- on HTTP versions < 1.1 support for chunked transfer is refused (only used in exploits)
11+
- requests conflicting configured or passed SCRIPT_NAME now produce a verbose error
12+
- Trailer fields are no longer inspected for headers indicating secure scheme
13+
14+
** Breaking changes **
15+
16+
- the limitations on valid characters in the HTTP method have been bounded to Internet Standards
17+
- requests specifying unsupported transfer coding (order) are refused by default (rare)
18+
- HTTP methods are no longer casefolded by default (IANA method registry contains none affacted)
19+
- HTTP methods containing the number sign (#) are no longer accepted by default (rare)
20+
- HTTP versions < 1.0 or >= 2.0 are no longer accepted by default (rare, only HTTP/1.1 is supported)
21+
- HTTP versions consisting of multiple digits or containing a prefix/suffix are no longer accepted
22+
- HTTP header field names Gunicorn cannot safely map to variables are silently dropped, as in other software
23+
- HTTP headers with empty field name are refused by default (no legitimate use cases, used in exploits)
24+
- requests with both Transfer-Encoding and Content-Length are refused by default (such a message might indicate an attempt to perform request smuggling)
25+
- empty transfer codings are no longer permitted (reportedly seen with really old & broken proxies)
26+
527
21.2.0 - 2023-07-19
628
===================
729

gunicorn/config.py

+127-1
Original file line numberDiff line numberDiff line change
@@ -2254,5 +2254,131 @@ class StripHeaderSpaces(Setting):
22542254
This is known to induce vulnerabilities and is not compliant with the HTTP/1.1 standard.
22552255
See https://portswigger.net/research/http-desync-attacks-request-smuggling-reborn.
22562256
2257-
Use with care and only if necessary.
2257+
Use with care and only if necessary. May be removed in a future version.
2258+
2259+
.. versionadded:: 20.0.1
2260+
"""
2261+
2262+
2263+
class PermitUnconventionalHTTPMethod(Setting):
2264+
name = "permit_unconventional_http_method"
2265+
section = "Server Mechanics"
2266+
cli = ["--permit-unconventional-http-method"]
2267+
validator = validate_bool
2268+
action = "store_true"
2269+
default = False
2270+
desc = """\
2271+
Permit HTTP methods not matching conventions, such as IANA registration guidelines
2272+
2273+
This permits request methods of length less than 3 or more than 20,
2274+
methods with lowercase characters or methods containing the # character.
2275+
HTTP methods are case sensitive by definition, and merely uppercase by convention.
2276+
2277+
This option is provided to diagnose backwards-incompatible changes.
2278+
2279+
Use with care and only if necessary. May be removed in a future version.
2280+
2281+
.. versionadded:: 22.0.0
2282+
"""
2283+
2284+
2285+
class PermitUnconventionalHTTPVersion(Setting):
2286+
name = "permit_unconventional_http_version"
2287+
section = "Server Mechanics"
2288+
cli = ["--permit-unconventional-http-version"]
2289+
validator = validate_bool
2290+
action = "store_true"
2291+
default = False
2292+
desc = """\
2293+
Permit HTTP version not matching conventions of 2023
2294+
2295+
This disables the refusal of likely malformed request lines.
2296+
It is unusual to specify HTTP 1 versions other than 1.0 and 1.1.
2297+
2298+
This option is provided to diagnose backwards-incompatible changes.
2299+
Use with care and only if necessary. May be removed in a future version.
2300+
2301+
.. versionadded:: 22.0.0
2302+
"""
2303+
2304+
2305+
class CasefoldHTTPMethod(Setting):
2306+
name = "casefold_http_method"
2307+
section = "Server Mechanics"
2308+
cli = ["--casefold-http-method"]
2309+
validator = validate_bool
2310+
action = "store_true"
2311+
default = False
2312+
desc = """\
2313+
Transform received HTTP methods to uppercase
2314+
2315+
HTTP methods are case sensitive by definition, and merely uppercase by convention.
2316+
2317+
This option is provided because previous versions of gunicorn defaulted to this behaviour.
2318+
2319+
Use with care and only if necessary. May be removed in a future version.
2320+
2321+
.. versionadded:: 22.0.0
2322+
"""
2323+
2324+
2325+
def validate_header_map_behaviour(val):
2326+
# FIXME: refactor all of this subclassing stdlib argparse
2327+
2328+
if val is None:
2329+
return
2330+
2331+
if not isinstance(val, str):
2332+
raise TypeError("Invalid type for casting: %s" % val)
2333+
if val.lower().strip() == "drop":
2334+
return "drop"
2335+
elif val.lower().strip() == "refuse":
2336+
return "refuse"
2337+
elif val.lower().strip() == "dangerous":
2338+
return "dangerous"
2339+
else:
2340+
raise ValueError("Invalid header map behaviour: %s" % val)
2341+
2342+
2343+
class HeaderMap(Setting):
2344+
name = "header_map"
2345+
section = "Server Mechanics"
2346+
cli = ["--header-map"]
2347+
validator = validate_header_map_behaviour
2348+
default = "drop"
2349+
desc = """\
2350+
Configure how header field names are mapped into environ
2351+
2352+
Headers containing underscores are permitted by RFC9110,
2353+
but gunicorn joining headers of different names into
2354+
the same environment variable will dangerously confuse applications as to which is which.
2355+
2356+
The safe default ``drop`` is to silently drop headers that cannot be unambiguously mapped.
2357+
The value ``refuse`` will return an error if a request contains *any* such header.
2358+
The value ``dangerous`` matches the previous, not advisabble, behaviour of mapping different
2359+
header field names into the same environ name.
2360+
2361+
Use with care and only if necessary and after considering if your problem could
2362+
instead be solved by specifically renaming or rewriting only the intended headers
2363+
on a proxy in front of Gunicorn.
2364+
2365+
.. versionadded:: 22.0.0
2366+
"""
2367+
2368+
2369+
class TolerateDangerousFraming(Setting):
2370+
name = "tolerate_dangerous_framing"
2371+
section = "Server Mechanics"
2372+
cli = ["--tolerate-dangerous-framing"]
2373+
validator = validate_bool
2374+
action = "store_true"
2375+
default = False
2376+
desc = """\
2377+
Process requests with both Transfer-Encoding and Content-Length
2378+
2379+
This is known to induce vulnerabilities, but not strictly forbidden by RFC9112.
2380+
2381+
Use with care and only if necessary. May be removed in a future version.
2382+
2383+
.. versionadded:: 22.0.0
22582384
"""

gunicorn/http/body.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def parse_trailers(self, unreader, data):
5151
if done:
5252
unreader.unread(buf.getvalue()[2:])
5353
return b""
54-
self.req.trailers = self.req.parse_headers(buf.getvalue()[:idx])
54+
self.req.trailers = self.req.parse_headers(buf.getvalue()[:idx], from_trailer=True)
5555
unreader.unread(buf.getvalue()[idx + 4:])
5656

5757
def parse_chunked(self, unreader):
@@ -85,11 +85,13 @@ def parse_chunk_size(self, unreader, data=None):
8585
data = buf.getvalue()
8686
line, rest_chunk = data[:idx], data[idx + 2:]
8787

88-
chunk_size = line.split(b";", 1)[0].strip()
89-
try:
90-
chunk_size = int(chunk_size, 16)
91-
except ValueError:
88+
# RFC9112 7.1.1: BWS before chunk-ext - but ONLY then
89+
chunk_size, *chunk_ext = line.split(b";", 1)
90+
if chunk_ext:
91+
chunk_size = chunk_size.rstrip(b" \t")
92+
if any(n not in b"0123456789abcdefABCDEF" for n in chunk_size):
9293
raise InvalidChunkSize(chunk_size)
94+
chunk_size = int(chunk_size, 16)
9395

9496
if chunk_size == 0:
9597
try:

gunicorn/http/errors.py

+18
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ def __str__(self):
2222
return "No more data after: %r" % self.buf
2323

2424

25+
class ConfigurationProblem(ParseException):
26+
def __init__(self, info):
27+
self.info = info
28+
self.code = 500
29+
30+
def __str__(self):
31+
return "Configuration problem: %s" % self.info
32+
33+
2534
class InvalidRequestLine(ParseException):
2635
def __init__(self, req):
2736
self.req = req
@@ -64,6 +73,15 @@ def __str__(self):
6473
return "Invalid HTTP header name: %r" % self.hdr
6574

6675

76+
class UnsupportedTransferCoding(ParseException):
77+
def __init__(self, hdr):
78+
self.hdr = hdr
79+
self.code = 501
80+
81+
def __str__(self):
82+
return "Unsupported transfer coding: %r" % self.hdr
83+
84+
6785
class InvalidChunkSize(IOError):
6886
def __init__(self, data):
6987
self.data = data

0 commit comments

Comments
 (0)