summaryrefslogtreecommitdiffstats
path: root/Lib/test/test_urllib2.py
diff options
context:
space:
mode:
authorVictor Stinner <vstinner@python.org>2020-04-02 00:52:20 (GMT)
committerGitHub <noreply@github.com>2020-04-02 00:52:20 (GMT)
commit0b297d4ff1c0e4480ad33acae793fbaf4bf015b4 (patch)
tree7f39cf8cddaf63245f29e784ee570586d902afed /Lib/test/test_urllib2.py
parentd57cf557366584539f400db523b555296487e8f5 (diff)
downloadcpython-0b297d4ff1c0e4480ad33acae793fbaf4bf015b4.zip
cpython-0b297d4ff1c0e4480ad33acae793fbaf4bf015b4.tar.gz
cpython-0b297d4ff1c0e4480ad33acae793fbaf4bf015b4.tar.bz2
bpo-39503: CVE-2020-8492: Fix AbstractBasicAuthHandler (GH-18284)
The AbstractBasicAuthHandler class of the urllib.request module uses an inefficient regular expression which can be exploited by an attacker to cause a denial of service. Fix the regex to prevent the catastrophic backtracking. Vulnerability reported by Ben Caller and Matt Schwager. AbstractBasicAuthHandler of urllib.request now parses all WWW-Authenticate HTTP headers and accepts multiple challenges per header: use the realm of the first Basic challenge. Co-Authored-By: Serhiy Storchaka <storchaka@gmail.com>
Diffstat (limited to 'Lib/test/test_urllib2.py')
-rw-r--r--Lib/test/test_urllib2.py90
1 files changed, 57 insertions, 33 deletions
diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py
index 8abedaa..e69ac3e 100644
--- a/Lib/test/test_urllib2.py
+++ b/Lib/test/test_urllib2.py
@@ -1446,40 +1446,64 @@ class HandlerTests(unittest.TestCase):
bypass = {'exclude_simple': True, 'exceptions': []}
self.assertTrue(_proxy_bypass_macosx_sysconf('test', bypass))
- def test_basic_auth(self, quote_char='"'):
- opener = OpenerDirector()
- password_manager = MockPasswordManager()
- auth_handler = urllib.request.HTTPBasicAuthHandler(password_manager)
- realm = "ACME Widget Store"
- http_handler = MockHTTPHandler(
- 401, 'WWW-Authenticate: Basic realm=%s%s%s\r\n\r\n' %
- (quote_char, realm, quote_char))
- opener.add_handler(auth_handler)
- opener.add_handler(http_handler)
- self._test_basic_auth(opener, auth_handler, "Authorization",
- realm, http_handler, password_manager,
- "http://acme.example.com/protected",
- "http://acme.example.com/protected",
- )
-
- def test_basic_auth_with_single_quoted_realm(self):
- self.test_basic_auth(quote_char="'")
-
- def test_basic_auth_with_unquoted_realm(self):
- opener = OpenerDirector()
- password_manager = MockPasswordManager()
- auth_handler = urllib.request.HTTPBasicAuthHandler(password_manager)
- realm = "ACME Widget Store"
- http_handler = MockHTTPHandler(
- 401, 'WWW-Authenticate: Basic realm=%s\r\n\r\n' % realm)
- opener.add_handler(auth_handler)
- opener.add_handler(http_handler)
- with self.assertWarns(UserWarning):
+ def check_basic_auth(self, headers, realm):
+ with self.subTest(realm=realm, headers=headers):
+ opener = OpenerDirector()
+ password_manager = MockPasswordManager()
+ auth_handler = urllib.request.HTTPBasicAuthHandler(password_manager)
+ body = '\r\n'.join(headers) + '\r\n\r\n'
+ http_handler = MockHTTPHandler(401, body)
+ opener.add_handler(auth_handler)
+ opener.add_handler(http_handler)
self._test_basic_auth(opener, auth_handler, "Authorization",
- realm, http_handler, password_manager,
- "http://acme.example.com/protected",
- "http://acme.example.com/protected",
- )
+ realm, http_handler, password_manager,
+ "http://acme.example.com/protected",
+ "http://acme.example.com/protected")
+
+ def test_basic_auth(self):
+ realm = "realm2@example.com"
+ realm2 = "realm2@example.com"
+ basic = f'Basic realm="{realm}"'
+ basic2 = f'Basic realm="{realm2}"'
+ other_no_realm = 'Otherscheme xxx'
+ digest = (f'Digest realm="{realm2}", '
+ f'qop="auth, auth-int", '
+ f'nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", '
+ f'opaque="5ccc069c403ebaf9f0171e9517f40e41"')
+ for realm_str in (
+ # test "quote" and 'quote'
+ f'Basic realm="{realm}"',
+ f"Basic realm='{realm}'",
+
+ # charset is ignored
+ f'Basic realm="{realm}", charset="UTF-8"',
+
+ # Multiple challenges per header
+ f'{basic}, {basic2}',
+ f'{basic}, {other_no_realm}',
+ f'{other_no_realm}, {basic}',
+ f'{basic}, {digest}',
+ f'{digest}, {basic}',
+ ):
+ headers = [f'WWW-Authenticate: {realm_str}']
+ self.check_basic_auth(headers, realm)
+
+ # no quote: expect a warning
+ with support.check_warnings(("Basic Auth Realm was unquoted",
+ UserWarning)):
+ headers = [f'WWW-Authenticate: Basic realm={realm}']
+ self.check_basic_auth(headers, realm)
+
+ # Multiple headers: one challenge per header.
+ # Use the first Basic realm.
+ for challenges in (
+ [basic, basic2],
+ [basic, digest],
+ [digest, basic],
+ ):
+ headers = [f'WWW-Authenticate: {challenge}'
+ for challenge in challenges]
+ self.check_basic_auth(headers, realm)
def test_proxy_basic_auth(self):
opener = OpenerDirector()