Headline
CVE-2021-33880: Use constant-time comparison for passwords. · aaugustin/websockets@547a26b
The aaugustin websockets library before 9.1 for Python has an Observable Timing Discrepancy on servers when HTTP Basic Authentication is enabled with basic_auth_protocol_factory(credentials=…). An attacker may be able to guess a password via a timing attack.
@@ -6,6 +6,7 @@
import functools
import hmac
import http
from typing import Any, Awaitable, Callable, Iterable, Optional, Tuple, Union, cast
@@ -132,24 +133,23 @@ def basic_auth_protocol_factory(
if credentials is not None:
if is_credentials(credentials):
async def check_credentials(username: str, password: str) -> bool:
return (username, password) == credentials
credentials_list = [cast(Credentials, credentials)]
elif isinstance(credentials, Iterable):
credentials_list = list(credentials)
if all(is_credentials(item) for item in credentials_list):
credentials_dict = dict(credentials_list)
async def check_credentials(username: str, password: str) -> bool:
return credentials_dict.get(username) == password
else:
if not all(is_credentials(item) for item in credentials_list):
raise TypeError(f"invalid credentials argument: {credentials}")
else:
raise TypeError(f"invalid credentials argument: {credentials}")
credentials_dict = dict(credentials_list)
async def check_credentials(username: str, password: str) -> bool:
try:
expected_password = credentials_dict[username]
except KeyError:
return False
return hmac.compare_digest(expected_password, password)
if create_protocol is None:
# Not sure why mypy cannot figure this out.
create_protocol = cast(
@@ -158,5 +158,7 @@ async def check_credentials(username: str, password: str) -> bool:
)
return functools.partial(
create_protocol, realm=realm, check_credentials=check_credentials
create_protocol,
realm=realm,
check_credentials=check_credentials,
)