Skip to content

Commit fc697c7

Browse files
author
Stephen Reichling
authored
Merge pull request #5 from ucodery/cve-26116
Address CVE-2020-26116 for httplib methods
2 parents 02daa01 + 15b0c63 commit fc697c7

2 files changed

Lines changed: 41 additions & 2 deletions

File tree

Lib/httplib.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,10 @@
257257
# _is_allowed_url_pchars_re = re.compile(r"^[/!$&'()*+,;=:@%a-zA-Z0-9._~-]+$")
258258
# We are more lenient for assumed real world compatibility purposes.
259259

260+
# These characters are not allowed within HTTP method names
261+
# to prevent http header injection.
262+
_contains_disallowed_method_pchar_re = re.compile('[\x00-\x1f]')
263+
260264
# We always set the Content-Length header for these methods because some
261265
# servers will otherwise respond with a 411
262266
_METHODS_EXPECTING_BODY = {'PATCH', 'POST', 'PUT'}
@@ -935,6 +939,8 @@ def putrequest(self, method, url, skip_host=0, skip_accept_encoding=0):
935939
else:
936940
raise CannotSendRequest()
937941

942+
self._validate_method(method)
943+
938944
# Save the method for use later in the response phase
939945
self._method = method
940946

@@ -1020,6 +1026,16 @@ def _encode_request(self, request):
10201026
# On Python 2, request is already encoded (default)
10211027
return request
10221028

1029+
def _validate_method(self, method):
1030+
"""Validate a method name for putrequest."""
1031+
# prevent http header injection
1032+
match = _contains_disallowed_method_pchar_re.search(method)
1033+
if match:
1034+
raise ValueError(
1035+
"method can't contain control characters. %r "
1036+
"(found at least %r)"
1037+
% (method, match.group()))
1038+
10231039
def _validate_path(self, url):
10241040
"""Validate a url for putrequest."""
10251041
# Prevent CVE-2019-9740.

Lib/test/test_httplib.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,28 @@ def test_invalid_headers(self):
385385
conn.putheader(name, value)
386386

387387

388+
class HttpMethodTests(TestCase):
389+
def test_invalid_method_names(self):
390+
methods = (
391+
'GET\r',
392+
'POST\n',
393+
'PUT\n\r',
394+
'POST\nValue',
395+
'POST\nHOST:abc',
396+
'GET\nrHost:abc\n',
397+
'POST\rRemainder:\r',
398+
'GET\rHOST:\n',
399+
'\nPUT'
400+
)
401+
402+
for method in methods:
403+
with self.assertRaisesRegexp(
404+
ValueError, "method can't contain control characters"):
405+
conn = httplib.HTTPConnection('example.com')
406+
conn.sock = FakeSocket(None)
407+
conn.request(method=method, url="/")
408+
409+
388410
class BasicTest(TestCase):
389411
def test_status_lines(self):
390412
# Test HTTP status lines
@@ -1009,8 +1031,9 @@ def create_connection(address, timeout=None, source_address=None):
10091031

10101032
@test_support.reap_threads
10111033
def test_main(verbose=None):
1012-
test_support.run_unittest(HeaderTests, OfflineTest, BasicTest, TimeoutTest,
1013-
HTTPTest, HTTPSTest, SourceAddressTest,
1034+
test_support.run_unittest(HeaderTests, OfflineTest, HttpMethodTests,
1035+
BasicTest, TimeoutTest, HTTPTest, HTTPSTest,
1036+
SourceAddressTest,
10141037
TunnelTests)
10151038

10161039
if __name__ == '__main__':

0 commit comments

Comments
 (0)