mirror of
				https://github.com/django/django.git
				synced 2025-10-29 08:36:09 +00:00 
			
		
		
		
	[5.0.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:
		| @@ -40,14 +40,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 | ||||||
|   | |||||||
| @@ -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. | ||||||
|   | |||||||
| @@ -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 | ||||||
| ======== | ======== | ||||||
|  |  | ||||||
|   | |||||||
| @@ -565,6 +565,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, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user