+47
-2
lines changedFilter options
+47
-2
lines changed Original file line number Diff line number Diff line change
@@ -43,14 +43,20 @@ def check_password(password, encoded, setter=None, preferred="default"):
43
43
If setter is specified, it'll be called when you need to
44
44
regenerate the password.
45
45
"""
46
-
if password is None or not is_password_usable(encoded):
47
-
return False
46
+
fake_runtime = password is None or not is_password_usable(encoded)
48
47
49
48
preferred = get_hasher(preferred)
50
49
try:
51
50
hasher = identify_hasher(encoded)
52
51
except ValueError:
53
52
# encoded is gibberish or uses a hasher that's no longer installed.
53
+
fake_runtime = True
54
+
55
+
if fake_runtime:
56
+
# Run the default password hasher once to reduce the timing difference
57
+
# between an existing user with an unusable password and a nonexistent
58
+
# user or missing hasher (similar to #20760).
59
+
make_password(get_random_string(UNUSABLE_PASSWORD_SUFFIX_LENGTH))
54
60
return False
55
61
56
62
hasher_changed = hasher.algorithm != preferred.algorithm
Original file line number Diff line number Diff line change
@@ -13,3 +13,10 @@ CVE-2024-38875: Potential denial-of-service vulnerability in ``django.utils.html
13
13
:tfilter:`urlize` and :tfilter:`urlizetrunc` were subject to a potential
14
14
denial-of-service attack via certain inputs with a very large number of
15
15
brackets.
16
+
17
+
CVE-2024-39329: Username enumeration through timing difference for users with unusable passwords
18
+
================================================================================================
19
+
20
+
The :meth:`~django.contrib.auth.backends.ModelBackend.authenticate()` method
21
+
allowed remote attackers to enumerate users via a timing attack involving login
22
+
requests for users with unusable passwords.
Original file line number Diff line number Diff line change
@@ -613,6 +613,38 @@ def test_check_password_calls_harden_runtime(self):
613
613
check_password("wrong_password", encoded)
614
614
self.assertEqual(hasher.harden_runtime.call_count, 1)
615
615
616
+
def test_check_password_calls_make_password_to_fake_runtime(self):
617
+
hasher = get_hasher("default")
618
+
cases = [
619
+
(None, None, None), # no plain text password provided
620
+
("foo", make_password(password=None), None), # unusable encoded
621
+
("letmein", make_password(password="letmein"), ValueError), # valid encoded
622
+
]
623
+
for password, encoded, hasher_side_effect in cases:
624
+
with (
625
+
self.subTest(encoded=encoded),
626
+
mock.patch(
627
+
"django.contrib.auth.hashers.identify_hasher",
628
+
side_effect=hasher_side_effect,
629
+
) as mock_identify_hasher,
630
+
mock.patch(
631
+
"django.contrib.auth.hashers.make_password"
632
+
) as mock_make_password,
633
+
mock.patch(
634
+
"django.contrib.auth.hashers.get_random_string",
635
+
side_effect=lambda size: "x" * size,
636
+
),
637
+
mock.patch.object(hasher, "verify"),
638
+
):
639
+
# Ensure make_password is called to standardize timing.
640
+
check_password(password, encoded)
641
+
self.assertEqual(hasher.verify.call_count, 0)
642
+
self.assertEqual(mock_identify_hasher.mock_calls, [mock.call(encoded)])
643
+
self.assertEqual(
644
+
mock_make_password.mock_calls,
645
+
[mock.call("x" * UNUSABLE_PASSWORD_SUFFIX_LENGTH)],
646
+
)
647
+
616
648
def test_encode_invalid_salt(self):
617
649
hasher_classes = [
618
650
MD5PasswordHasher,
You can’t perform that action at this time.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4