From ea346c64a18139459a27de7c6a89bd6b83e5a049 Mon Sep 17 00:00:00 2001 From: AMIR <31338382+amiremohamadi@users.noreply.github.com> Date: Sun, 19 Jul 2020 00:46:10 +0430 Subject: [PATCH] bpo-39603: Prevent header injection in http methods (GH-18485) reject control chars in http method in http.client.putrequest to prevent http header injection (cherry picked from commit 8ca8a2e8fb068863c1138f07e3098478ef8be12e) Co-authored-by: AMIR <31338382+amiremohamadi@users.noreply.github.com> --- Lib/http/client.py | 15 +++++++++++++ Lib/test/test_httplib.py | 22 +++++++++++++++++++ .../2020-02-12-14-17-39.bpo-39603.Gt3RSg.rst | 2 ++ 3 files changed, 39 insertions(+) create mode 100644 Misc/NEWS.d/next/Security/2020-02-12-14-17-39.bpo-39603.Gt3RSg.rst diff --git a/Lib/http/client.py b/Lib/http/client.py index 09c57af865ee0a..04cd8f7d849861 100644 --- a/Lib/http/client.py +++ b/Lib/http/client.py @@ -150,6 +150,10 @@ # _is_allowed_url_pchars_re = re.compile(r"^[/!$&'()*+,;=:@%a-zA-Z0-9._~-]+$") # We are more lenient for assumed real world compatibility purposes. +# These characters are not allowed within HTTP method names +# to prevent http header injection. +_contains_disallowed_method_pchar_re = re.compile('[\x00-\x1f]') + # We always set the Content-Length header for these methods because some # servers will otherwise respond with a 411 _METHODS_EXPECTING_BODY = {'PATCH', 'POST', 'PUT'} @@ -1109,6 +1113,8 @@ def putrequest(self, method, url, skip_host=False, else: raise CannotSendRequest(self.__state) + self._validate_method(method) + # Save the method for use later in the response phase self._method = method @@ -1199,6 +1205,15 @@ def _encode_request(self, request): # ASCII also helps prevent CVE-2019-9740. return request.encode('ascii') + def _validate_method(self, method): + """Validate a method name for putrequest.""" + # prevent http header injection + match = _contains_disallowed_method_pchar_re.search(method) + if match: + raise ValueError( + f"method can't contain control characters. {method!r} " + f"(found at least {match.group()!r})") + def _validate_path(self, url): """Validate a url for putrequest.""" # Prevent CVE-2019-9740. diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py index 891393ab869e69..3fa0691d3ad8f2 100644 --- a/Lib/test/test_httplib.py +++ b/Lib/test/test_httplib.py @@ -363,6 +363,28 @@ def test_headers_debuglevel(self): self.assertEqual(lines[3], "header: Second: val2") +class HttpMethodTests(TestCase): + def test_invalid_method_names(self): + methods = ( + 'GET\r', + 'POST\n', + 'PUT\n\r', + 'POST\nValue', + 'POST\nHOST:abc', + 'GET\nrHost:abc\n', + 'POST\rRemainder:\r', + 'GET\rHOST:\n', + '\nPUT' + ) + + for method in methods: + with self.assertRaisesRegex( + ValueError, "method can't contain control characters"): + conn = client.HTTPConnection('example.com') + conn.sock = FakeSocket(None) + conn.request(method=method, url="/") + + class TransferEncodingTest(TestCase): expected_body = b"It's just a flesh wound" diff --git a/Misc/NEWS.d/next/Security/2020-02-12-14-17-39.bpo-39603.Gt3RSg.rst b/Misc/NEWS.d/next/Security/2020-02-12-14-17-39.bpo-39603.Gt3RSg.rst new file mode 100644 index 00000000000000..990affc3edd9d8 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2020-02-12-14-17-39.bpo-39603.Gt3RSg.rst @@ -0,0 +1,2 @@ +Prevent http header injection by rejecting control characters in +http.client.putrequest(...).
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: