1
0
mirror of https://github.com/django/django.git synced 2025-06-05 03:29:12 +00:00

[4.2.x] 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 79f3687642
commit 156d3186c9
3 changed files with 47 additions and 2 deletions

View File

@ -43,14 +43,20 @@ def check_password(password, encoded, setter=None, preferred="default"):
If setter is specified, it'll be called when you need to If setter is specified, it'll be called when you need to
regenerate the password. regenerate the password.
""" """
if password is None or not is_password_usable(encoded): fake_runtime = password is None or not is_password_usable(encoded)
return 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 return 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

@ -613,6 +613,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,