1
0
mirror of https://github.com/django/django.git synced 2024-12-22 17:16:24 +00:00

Fixed CVE-2024-39329 -- Standarized timing of verify_password() when checking unusuable passwords.

Refs #20760.

Thanks Michael Manfre for the fix and to Adam Johnson for the review.
This commit is contained in:
Michael Manfre 2024-06-14 22:12:58 -04:00 committed by Natalia
parent d666457453
commit 5d86458579
4 changed files with 54 additions and 2 deletions

View File

@ -39,14 +39,20 @@ def verify_password(password, encoded, preferred="default"):
three part encoded digest, and the second whether to regenerate the three part encoded digest, and the second whether to regenerate the
password. password.
""" """
if password is None or not is_password_usable(encoded): fake_runtime = password is None or not is_password_usable(encoded)
return False, False
preferred = get_hasher(preferred) preferred = get_hasher(preferred)
try: try:
hasher = identify_hasher(encoded) hasher = identify_hasher(encoded)
except ValueError: except ValueError:
# encoded is gibberish or uses a hasher that's no longer installed. # encoded is gibberish or uses a hasher that's no longer installed.
fake_runtime = True
if fake_runtime:
# Run the default password hasher once to reduce the timing difference
# between an existing user with an unusable password and a nonexistent
# user or missing hasher (similar to #20760).
make_password(get_random_string(UNUSABLE_PASSWORD_SUFFIX_LENGTH))
return False, False return False, False
hasher_changed = hasher.algorithm != preferred.algorithm hasher_changed = hasher.algorithm != preferred.algorithm

View File

@ -13,3 +13,10 @@ CVE-2024-38875: Potential denial-of-service vulnerability in ``django.utils.html
:tfilter:`urlize` and :tfilter:`urlizetrunc` were subject to a potential :tfilter:`urlize` and :tfilter:`urlizetrunc` were subject to a potential
denial-of-service attack via certain inputs with a very large number of denial-of-service attack via certain inputs with a very large number of
brackets. brackets.
CVE-2024-39329: Username enumeration through timing difference for users with unusable passwords
================================================================================================
The :meth:`~django.contrib.auth.backends.ModelBackend.authenticate()` method
allowed remote attackers to enumerate users via a timing attack involving login
requests for users with unusable passwords.

View File

@ -14,6 +14,13 @@ CVE-2024-38875: Potential denial-of-service vulnerability in ``django.utils.html
denial-of-service attack via certain inputs with a very large number of denial-of-service attack via certain inputs with a very large number of
brackets. brackets.
CVE-2024-39329: Username enumeration through timing difference for users with unusable passwords
================================================================================================
The :meth:`~django.contrib.auth.backends.ModelBackend.authenticate()` method
allowed remote attackers to enumerate users via a timing attack involving login
requests for users with unusable passwords.
Bugfixes Bugfixes
======== ========

View File

@ -452,6 +452,38 @@ class TestUtilsHashPass(SimpleTestCase):
check_password("wrong_password", encoded) check_password("wrong_password", encoded)
self.assertEqual(hasher.harden_runtime.call_count, 1) self.assertEqual(hasher.harden_runtime.call_count, 1)
def test_check_password_calls_make_password_to_fake_runtime(self):
hasher = get_hasher("default")
cases = [
(None, None, None), # no plain text password provided
("foo", make_password(password=None), None), # unusable encoded
("letmein", make_password(password="letmein"), ValueError), # valid encoded
]
for password, encoded, hasher_side_effect in cases:
with (
self.subTest(encoded=encoded),
mock.patch(
"django.contrib.auth.hashers.identify_hasher",
side_effect=hasher_side_effect,
) as mock_identify_hasher,
mock.patch(
"django.contrib.auth.hashers.make_password"
) as mock_make_password,
mock.patch(
"django.contrib.auth.hashers.get_random_string",
side_effect=lambda size: "x" * size,
),
mock.patch.object(hasher, "verify"),
):
# Ensure make_password is called to standardize timing.
check_password(password, encoded)
self.assertEqual(hasher.verify.call_count, 0)
self.assertEqual(mock_identify_hasher.mock_calls, [mock.call(encoded)])
self.assertEqual(
mock_make_password.mock_calls,
[mock.call("x" * UNUSABLE_PASSWORD_SUFFIX_LENGTH)],
)
def test_encode_invalid_salt(self): def test_encode_invalid_salt(self):
hasher_classes = [ hasher_classes = [
MD5PasswordHasher, MD5PasswordHasher,