Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitea9e240

Browse files
bpo-39503:CVE-2020-8492: Fix AbstractBasicAuthHandler (GH-18284) (GH-19296)
The AbstractBasicAuthHandler class of the urllib.request module usesan inefficient regular expression which can be exploited by anattacker to cause a denial of service. Fix the regex to prevent thecatastrophic backtracking. Vulnerability reported by Ben Callerand Matt Schwager.AbstractBasicAuthHandler of urllib.request now parses allWWW-Authenticate HTTP headers and accepts multiple challenges perheader: use the realm of the first Basic challenge.Co-Authored-By: Serhiy Storchaka <storchaka@gmail.com>Co-authored-by: Victor Stinner <vstinner@python.org>(cherry picked from commit0b297d4)
1 parent40fff1f commitea9e240

File tree

4 files changed

+115
-52
lines changed

4 files changed

+115
-52
lines changed

‎Lib/test/test_urllib2.py‎

Lines changed: 57 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1444,40 +1444,64 @@ def test_osx_proxy_bypass(self):
14441444
bypass= {'exclude_simple':True,'exceptions': []}
14451445
self.assertTrue(_proxy_bypass_macosx_sysconf('test',bypass))
14461446

1447-
deftest_basic_auth(self,quote_char='"'):
1448-
opener=OpenerDirector()
1449-
password_manager=MockPasswordManager()
1450-
auth_handler=urllib.request.HTTPBasicAuthHandler(password_manager)
1451-
realm="ACME Widget Store"
1452-
http_handler=MockHTTPHandler(
1453-
401,'WWW-Authenticate: Basic realm=%s%s%s\r\n\r\n'%
1454-
(quote_char,realm,quote_char))
1455-
opener.add_handler(auth_handler)
1456-
opener.add_handler(http_handler)
1457-
self._test_basic_auth(opener,auth_handler,"Authorization",
1458-
realm,http_handler,password_manager,
1459-
"http://acme.example.com/protected",
1460-
"http://acme.example.com/protected",
1461-
)
1462-
1463-
deftest_basic_auth_with_single_quoted_realm(self):
1464-
self.test_basic_auth(quote_char="'")
1465-
1466-
deftest_basic_auth_with_unquoted_realm(self):
1467-
opener=OpenerDirector()
1468-
password_manager=MockPasswordManager()
1469-
auth_handler=urllib.request.HTTPBasicAuthHandler(password_manager)
1470-
realm="ACME Widget Store"
1471-
http_handler=MockHTTPHandler(
1472-
401,'WWW-Authenticate: Basic realm=%s\r\n\r\n'%realm)
1473-
opener.add_handler(auth_handler)
1474-
opener.add_handler(http_handler)
1475-
withself.assertWarns(UserWarning):
1447+
defcheck_basic_auth(self,headers,realm):
1448+
withself.subTest(realm=realm,headers=headers):
1449+
opener=OpenerDirector()
1450+
password_manager=MockPasswordManager()
1451+
auth_handler=urllib.request.HTTPBasicAuthHandler(password_manager)
1452+
body='\r\n'.join(headers)+'\r\n\r\n'
1453+
http_handler=MockHTTPHandler(401,body)
1454+
opener.add_handler(auth_handler)
1455+
opener.add_handler(http_handler)
14761456
self._test_basic_auth(opener,auth_handler,"Authorization",
1477-
realm,http_handler,password_manager,
1478-
"http://acme.example.com/protected",
1479-
"http://acme.example.com/protected",
1480-
)
1457+
realm,http_handler,password_manager,
1458+
"http://acme.example.com/protected",
1459+
"http://acme.example.com/protected")
1460+
1461+
deftest_basic_auth(self):
1462+
realm="realm2@example.com"
1463+
realm2="realm2@example.com"
1464+
basic=f'Basic realm="{realm}"'
1465+
basic2=f'Basic realm="{realm2}"'
1466+
other_no_realm='Otherscheme xxx'
1467+
digest= (f'Digest realm="{realm2}", '
1468+
f'qop="auth, auth-int", '
1469+
f'nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", '
1470+
f'opaque="5ccc069c403ebaf9f0171e9517f40e41"')
1471+
forrealm_strin (
1472+
# test "quote" and 'quote'
1473+
f'Basic realm="{realm}"',
1474+
f"Basic realm='{realm}'",
1475+
1476+
# charset is ignored
1477+
f'Basic realm="{realm}", charset="UTF-8"',
1478+
1479+
# Multiple challenges per header
1480+
f'{basic},{basic2}',
1481+
f'{basic},{other_no_realm}',
1482+
f'{other_no_realm},{basic}',
1483+
f'{basic},{digest}',
1484+
f'{digest},{basic}',
1485+
):
1486+
headers= [f'WWW-Authenticate:{realm_str}']
1487+
self.check_basic_auth(headers,realm)
1488+
1489+
# no quote: expect a warning
1490+
withsupport.check_warnings(("Basic Auth Realm was unquoted",
1491+
UserWarning)):
1492+
headers= [f'WWW-Authenticate: Basic realm={realm}']
1493+
self.check_basic_auth(headers,realm)
1494+
1495+
# Multiple headers: one challenge per header.
1496+
# Use the first Basic realm.
1497+
forchallengesin (
1498+
[basic,basic2],
1499+
[basic,digest],
1500+
[digest,basic],
1501+
):
1502+
headers= [f'WWW-Authenticate:{challenge}'
1503+
forchallengeinchallenges]
1504+
self.check_basic_auth(headers,realm)
14811505

14821506
deftest_proxy_basic_auth(self):
14831507
opener=OpenerDirector()

‎Lib/urllib/request.py‎

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -945,8 +945,15 @@ class AbstractBasicAuthHandler:
945945

946946
# allow for double- and single-quoted realm values
947947
# (single quotes are a violation of the RFC, but appear in the wild)
948-
rx=re.compile('(?:.*,)*[\t]*([^\t]+)[\t]+'
949-
'realm=(["\']?)([^"\']*)\\2',re.I)
948+
rx=re.compile('(?:^|,)'# start of the string or ','
949+
'[\t]*'# optional whitespaces
950+
'([^\t]+)'# scheme like "Basic"
951+
'[\t]+'# mandatory whitespaces
952+
# realm=xxx
953+
# realm='xxx'
954+
# realm="xxx"
955+
'realm=(["\']?)([^"\']*)\\2',
956+
re.I)
950957

951958
# XXX could pre-emptively send auth info already accepted (RFC 2617,
952959
# end of section 2, and section 1.2 immediately after "credentials"
@@ -958,27 +965,51 @@ def __init__(self, password_mgr=None):
958965
self.passwd=password_mgr
959966
self.add_password=self.passwd.add_password
960967

968+
def_parse_realm(self,header):
969+
# parse WWW-Authenticate header: accept multiple challenges per header
970+
found_challenge=False
971+
formoinAbstractBasicAuthHandler.rx.finditer(header):
972+
scheme,quote,realm=mo.groups()
973+
ifquotenotin ['"',"'"]:
974+
warnings.warn("Basic Auth Realm was unquoted",
975+
UserWarning,3)
976+
977+
yield (scheme,realm)
978+
979+
found_challenge=True
980+
981+
ifnotfound_challenge:
982+
ifheader:
983+
scheme=header.split()[0]
984+
else:
985+
scheme=''
986+
yield (scheme,None)
987+
961988
defhttp_error_auth_reqed(self,authreq,host,req,headers):
962989
# host may be an authority (without userinfo) or a URL with an
963990
# authority
964-
# XXX could be multiple headers
965-
authreq=headers.get(authreq,None)
991+
headers=headers.get_all(authreq)
992+
ifnotheaders:
993+
# no header found
994+
return
966995

967-
ifauthreq:
968-
scheme=authreq.split()[0]
969-
ifscheme.lower()!='basic':
970-
raiseValueError("AbstractBasicAuthHandler does not"
971-
" support the following scheme: '%s'"%
972-
scheme)
973-
else:
974-
mo=AbstractBasicAuthHandler.rx.search(authreq)
975-
ifmo:
976-
scheme,quote,realm=mo.groups()
977-
ifquotenotin ['"',"'"]:
978-
warnings.warn("Basic Auth Realm was unquoted",
979-
UserWarning,2)
980-
ifscheme.lower()=='basic':
981-
returnself.retry_http_basic_auth(host,req,realm)
996+
unsupported=None
997+
forheaderinheaders:
998+
forscheme,realminself._parse_realm(header):
999+
ifscheme.lower()!='basic':
1000+
unsupported=scheme
1001+
continue
1002+
1003+
ifrealmisnotNone:
1004+
# Use the first matching Basic challenge.
1005+
# Ignore following challenges even if they use the Basic
1006+
# scheme.
1007+
returnself.retry_http_basic_auth(host,req,realm)
1008+
1009+
ifunsupportedisnotNone:
1010+
raiseValueError("AbstractBasicAuthHandler does not "
1011+
"support the following scheme: %r"
1012+
% (scheme,))
9821013

9831014
defretry_http_basic_auth(self,host,req,realm):
9841015
user,pw=self.passwd.find_user_password(realm,host)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:class:`~urllib.request.AbstractBasicAuthHandler` of:mod:`urllib.request`
2+
now parses all WWW-Authenticate HTTP headers and accepts multiple challenges
3+
per header: use the realm of the first Basic challenge.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
CVE-2020-8492: The:class:`~urllib.request.AbstractBasicAuthHandler` class of the
2+
:mod:`urllib.request` module uses an inefficient regular expression which can
3+
be exploited by an attacker to cause a denial of service. Fix the regex to
4+
prevent the catastrophic backtracking. Vulnerability reported by Ben Caller
5+
and Matt Schwager.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2026 Movatter.jp