VYPR
High severityNVD Advisory· Published Sep 23, 2013· Updated Apr 29, 2026

CVE-2013-1443

CVE-2013-1443

Description

The authentication framework (django.contrib.auth) in Django 1.4.x before 1.4.8, 1.5.x before 1.5.4, and 1.6.x before 1.6 beta 4 allows remote attackers to cause a denial of service (CPU consumption) via a long password which is then hashed.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
DjangoPyPI
>= 1.4, < 1.4.81.4.8
DjangoPyPI
>= 1.5, < 1.5.41.5.4

Affected products

16
  • cpe:2.3:a:djangoproject:django:1.4:*:*:*:*:*:*:*+ 15 more
    • cpe:2.3:a:djangoproject:django:1.4:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.4.1:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.4.2:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.4.4:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.4.5:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.4.6:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.4.7:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.6:beta1:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.6:beta2:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.6:beta3:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.5:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.5:alpha:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.5:beta:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.5.1:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.5.2:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.5.3:*:*:*:*:*:*:*

Patches

2
3f3d887a6844

[1.4.x] Ensure that passwords are never long enough for a DoS.

https://github.com/django/djangoRussell Keith-MageeSep 15, 2013via ghsa
3 files changed · +136 16
  • django/contrib/auth/forms.py+37 14 modified
    @@ -8,7 +8,10 @@
     
     from django.contrib.auth import authenticate
     from django.contrib.auth.models import User
    -from django.contrib.auth.hashers import UNUSABLE_PASSWORD, is_password_usable, get_hasher
    +from django.contrib.auth.hashers import (
    +    MAXIMUM_PASSWORD_LENGTH, UNUSABLE_PASSWORD,
    +    is_password_usable, get_hasher
    +)
     from django.contrib.auth.tokens import default_token_generator
     from django.contrib.sites.models import get_current_site
     
    @@ -70,10 +73,11 @@ class UserCreationForm(forms.ModelForm):
                 'invalid': _("This value may contain only letters, numbers and "
                              "@/./+/-/_ characters.")})
         password1 = forms.CharField(label=_("Password"),
    -        widget=forms.PasswordInput)
    +        widget=forms.PasswordInput, max_length=MAXIMUM_PASSWORD_LENGTH)
         password2 = forms.CharField(label=_("Password confirmation"),
             widget=forms.PasswordInput,
    -        help_text = _("Enter the same password as above, for verification."))
    +        max_length=MAXIMUM_PASSWORD_LENGTH,
    +        help_text=_("Enter the same password as above, for verification."))
     
         class Meta:
             model = User
    @@ -137,7 +141,11 @@ class AuthenticationForm(forms.Form):
         username/password logins.
         """
         username = forms.CharField(label=_("Username"), max_length=30)
    -    password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
    +    password = forms.CharField(
    +        label=_("Password"),
    +        widget=forms.PasswordInput,
    +        max_length=MAXIMUM_PASSWORD_LENGTH,
    +    )
     
         error_messages = {
             'invalid_login': _("Please enter a correct username and password. "
    @@ -250,10 +258,16 @@ class SetPasswordForm(forms.Form):
         error_messages = {
             'password_mismatch': _("The two password fields didn't match."),
         }
    -    new_password1 = forms.CharField(label=_("New password"),
    -                                    widget=forms.PasswordInput)
    -    new_password2 = forms.CharField(label=_("New password confirmation"),
    -                                    widget=forms.PasswordInput)
    +    new_password1 = forms.CharField(
    +        label=_("New password"),
    +        widget=forms.PasswordInput,
    +        max_length=MAXIMUM_PASSWORD_LENGTH,
    +    )
    +    new_password2 = forms.CharField(
    +        label=_("New password confirmation"),
    +        widget=forms.PasswordInput,
    +        max_length=MAXIMUM_PASSWORD_LENGTH,
    +    )
     
         def __init__(self, user, *args, **kwargs):
             self.user = user
    @@ -284,8 +298,11 @@ class PasswordChangeForm(SetPasswordForm):
             'password_incorrect': _("Your old password was entered incorrectly. "
                                     "Please enter it again."),
         })
    -    old_password = forms.CharField(label=_("Old password"),
    -                                   widget=forms.PasswordInput)
    +    old_password = forms.CharField(
    +        label=_("Old password"),
    +        widget=forms.PasswordInput,
    +        max_length=MAXIMUM_PASSWORD_LENGTH,
    +    )
     
         def clean_old_password(self):
             """
    @@ -307,10 +324,16 @@ class AdminPasswordChangeForm(forms.Form):
         error_messages = {
             'password_mismatch': _("The two password fields didn't match."),
         }
    -    password1 = forms.CharField(label=_("Password"),
    -                                widget=forms.PasswordInput)
    -    password2 = forms.CharField(label=_("Password (again)"),
    -                                widget=forms.PasswordInput)
    +    password1 = forms.CharField(
    +        label=_("Password"),
    +        widget=forms.PasswordInput,
    +        max_length=MAXIMUM_PASSWORD_LENGTH,
    +    )
    +    password2 = forms.CharField(
    +        label=_("Password (again)"),
    +        widget=forms.PasswordInput,
    +        max_length=MAXIMUM_PASSWORD_LENGTH,
    +    )
     
         def __init__(self, user, *args, **kwargs):
             self.user = user
    
  • django/contrib/auth/hashers.py+28 1 modified
    @@ -1,3 +1,4 @@
    +import functools
     import hashlib
     
     from django.conf import settings
    @@ -11,10 +12,23 @@
     
     
     UNUSABLE_PASSWORD = '!'  # This will never be a valid encoded hash
    +MAXIMUM_PASSWORD_LENGTH = 4096  # The maximum length a password can be to prevent DoS
     HASHERS = None  # lazily loaded from PASSWORD_HASHERS
     PREFERRED_HASHER = None  # defaults to first item in PASSWORD_HASHERS
     
     
    +def password_max_length(max_length):
    +    def inner(fn):
    +        @functools.wraps(fn)
    +        def wrapper(self, password, *args, **kwargs):
    +            if len(password) > max_length:
    +                raise ValueError("Invalid password; Must be less than or equal"
    +                                 " to %d bytes" % max_length)
    +            return fn(self, password, *args, **kwargs)
    +        return wrapper
    +    return inner
    +
    +
     def is_password_usable(encoded):
         return (encoded is not None and encoded != UNUSABLE_PASSWORD)
     
    @@ -202,6 +216,7 @@ class PBKDF2PasswordHasher(BasePasswordHasher):
         iterations = 10000
         digest = hashlib.sha256
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def encode(self, password, salt, iterations=None):
             assert password
             assert salt and '$' not in salt
    @@ -211,6 +226,7 @@ def encode(self, password, salt, iterations=None):
             hash = hash.encode('base64').strip()
             return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash)
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def verify(self, password, encoded):
             algorithm, iterations, salt, hash = encoded.split('$', 3)
             assert algorithm == self.algorithm
    @@ -256,11 +272,13 @@ def salt(self):
             bcrypt = self._load_library()
             return bcrypt.gensalt(self.rounds)
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def encode(self, password, salt):
             bcrypt = self._load_library()
             data = bcrypt.hashpw(password, salt)
             return "%s$%s" % (self.algorithm, data)
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def verify(self, password, encoded):
             algorithm, data = encoded.split('$', 1)
             assert algorithm == self.algorithm
    @@ -285,12 +303,14 @@ class SHA1PasswordHasher(BasePasswordHasher):
         """
         algorithm = "sha1"
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def encode(self, password, salt):
             assert password
             assert salt and '$' not in salt
             hash = hashlib.sha1(salt + password).hexdigest()
             return "%s$%s$%s" % (self.algorithm, salt, hash)
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def verify(self, password, encoded):
             algorithm, salt, hash = encoded.split('$', 2)
             assert algorithm == self.algorithm
    @@ -313,12 +333,14 @@ class MD5PasswordHasher(BasePasswordHasher):
         """
         algorithm = "md5"
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def encode(self, password, salt):
             assert password
             assert salt and '$' not in salt
             hash = hashlib.md5(salt + password).hexdigest()
             return "%s$%s$%s" % (self.algorithm, salt, hash)
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def verify(self, password, encoded):
             algorithm, salt, hash = encoded.split('$', 2)
             assert algorithm == self.algorithm
    @@ -349,11 +371,13 @@ class UnsaltedSHA1PasswordHasher(BasePasswordHasher):
         def salt(self):
             return ''
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def encode(self, password, salt):
             assert salt == ''
             hash = hashlib.sha1(password).hexdigest()
             return 'sha1$$%s' % hash
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def verify(self, password, encoded):
             encoded_2 = self.encode(password, '')
             return constant_time_compare(encoded, encoded_2)
    @@ -383,10 +407,12 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher):
         def salt(self):
             return ''
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def encode(self, password, salt):
             assert salt == ''
             return hashlib.md5(password).hexdigest()
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def verify(self, password, encoded):
             if len(encoded) == 37 and encoded.startswith('md5$$'):
                 encoded = encoded[5:]
    @@ -412,13 +438,15 @@ class CryptPasswordHasher(BasePasswordHasher):
         def salt(self):
             return get_random_string(2)
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def encode(self, password, salt):
             crypt = self._load_library()
             assert len(salt) == 2
             data = crypt.crypt(password, salt)
             # we don't need to store the salt, but Django used to do this
             return "%s$%s$%s" % (self.algorithm, '', data)
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def verify(self, password, encoded):
             crypt = self._load_library()
             algorithm, salt, data = encoded.split('$', 2)
    @@ -433,4 +461,3 @@ def safe_summary(self, encoded):
                 (_('salt'), salt),
                 (_('hash'), mask_hash(data, show=3)),
             ])
    -
    
  • django/contrib/auth/tests/hashers.py+71 1 modified
    @@ -1,7 +1,8 @@
     from django.conf.global_settings import PASSWORD_HASHERS as default_hashers
     from django.contrib.auth.hashers import (is_password_usable,
         check_password, make_password, PBKDF2PasswordHasher, load_hashers,
    -    PBKDF2SHA1PasswordHasher, get_hasher, UNUSABLE_PASSWORD)
    +    PBKDF2SHA1PasswordHasher, get_hasher, UNUSABLE_PASSWORD,
    +    MAXIMUM_PASSWORD_LENGTH, password_max_length)
     from django.utils import unittest
     from django.utils.unittest import skipUnless
     from django.test.utils import override_settings
    @@ -28,6 +29,12 @@ def test_simple(self):
             self.assertTrue(is_password_usable(encoded))
             self.assertTrue(check_password(u'letmein', encoded))
             self.assertFalse(check_password('letmeinz', encoded))
    +        # Long password
    +        self.assertRaises(
    +            ValueError,
    +            make_password,
    +            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
    +        )
     
         def test_pkbdf2(self):
             encoded = make_password('letmein', 'seasalt', 'pbkdf2_sha256')
    @@ -36,6 +43,14 @@ def test_pkbdf2(self):
             self.assertTrue(is_password_usable(encoded))
             self.assertTrue(check_password(u'letmein', encoded))
             self.assertFalse(check_password('letmeinz', encoded))
    +        # Long password
    +        self.assertRaises(
    +            ValueError,
    +            make_password,
    +            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
    +            "seasalt",
    +            "pbkdf2_sha256",
    +        )
     
         def test_sha1(self):
             encoded = make_password('letmein', 'seasalt', 'sha1')
    @@ -44,6 +59,14 @@ def test_sha1(self):
             self.assertTrue(is_password_usable(encoded))
             self.assertTrue(check_password(u'letmein', encoded))
             self.assertFalse(check_password('letmeinz', encoded))
    +        # Long password
    +        self.assertRaises(
    +            ValueError,
    +            make_password,
    +            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
    +            "seasalt",
    +            "sha1",
    +        )
     
         def test_md5(self):
             encoded = make_password('letmein', 'seasalt', 'md5')
    @@ -52,6 +75,14 @@ def test_md5(self):
             self.assertTrue(is_password_usable(encoded))
             self.assertTrue(check_password(u'letmein', encoded))
             self.assertFalse(check_password('letmeinz', encoded))
    +        # Long password
    +        self.assertRaises(
    +            ValueError,
    +            make_password,
    +            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
    +            "seasalt",
    +            "md5",
    +        )
     
         def test_unsalted_md5(self):
             encoded = make_password('letmein', '', 'unsalted_md5')
    @@ -64,6 +95,14 @@ def test_unsalted_md5(self):
             self.assertTrue(is_password_usable(alt_encoded))
             self.assertTrue(check_password(u'letmein', alt_encoded))
             self.assertFalse(check_password('letmeinz', alt_encoded))
    +        # Long password
    +        self.assertRaises(
    +            ValueError,
    +            make_password,
    +            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
    +            "",
    +            "unsalted_md5",
    +        )
     
         def test_unsalted_sha1(self):
             encoded = make_password('letmein', '', 'unsalted_sha1')
    @@ -74,6 +113,14 @@ def test_unsalted_sha1(self):
             # Raw SHA1 isn't acceptable
             alt_encoded = encoded[6:]
             self.assertRaises(ValueError, check_password, 'letmein', alt_encoded)
    +        # Long password
    +        self.assertRaises(
    +            ValueError,
    +            make_password,
    +            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
    +            "",
    +            "unslated_sha1",
    +        )
     
         @skipUnless(crypt, "no crypt module to generate password.")
         def test_crypt(self):
    @@ -82,6 +129,14 @@ def test_crypt(self):
             self.assertTrue(is_password_usable(encoded))
             self.assertTrue(check_password(u'letmein', encoded))
             self.assertFalse(check_password('letmeinz', encoded))
    +        # Long password
    +        self.assertRaises(
    +            ValueError,
    +            make_password,
    +            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
    +            "seasalt",
    +            "crypt",
    +        )
     
         @skipUnless(bcrypt, "py-bcrypt not installed")
         def test_bcrypt(self):
    @@ -90,6 +145,13 @@ def test_bcrypt(self):
             self.assertTrue(encoded.startswith('bcrypt$'))
             self.assertTrue(check_password(u'letmein', encoded))
             self.assertFalse(check_password('letmeinz', encoded))
    +        # Long password
    +        self.assertRaises(
    +            ValueError,
    +            make_password,
    +            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
    +            hasher="bcrypt",
    +        )
     
         def test_unusable(self):
             encoded = make_password(None)
    @@ -105,6 +167,14 @@ def doit():
                 make_password('letmein', hasher='lolcat')
             self.assertRaises(ValueError, doit)
     
    +    def test_max_password_length_decorator(self):
    +        @password_max_length(10)
    +        def encode(s, password, salt):
    +            return True
    +
    +        self.assertTrue(encode(None, b"1234", b"1234"))
    +        self.assertRaises(ValueError, encode, None, b"1234567890A", b"1234")
    +
         def test_low_level_pkbdf2(self):
             hasher = PBKDF2PasswordHasher()
             encoded = hasher.encode('letmein', 'seasalt')
    
22b74fa09d7c

[1.5.x] Ensure that passwords are never long enough for a DoS.

https://github.com/django/djangoRussell Keith-MageeSep 15, 2013via ghsa
3 files changed · +134 15
  • django/contrib/auth/forms.py+35 13 modified
    @@ -12,7 +12,9 @@
     
     from django.contrib.auth import authenticate, get_user_model
     from django.contrib.auth.models import User
    -from django.contrib.auth.hashers import UNUSABLE_PASSWORD, identify_hasher
    +from django.contrib.auth.hashers import (
    +    MAXIMUM_PASSWORD_LENGTH, UNUSABLE_PASSWORD, identify_hasher,
    +)
     from django.contrib.auth.tokens import default_token_generator
     from django.contrib.sites.models import get_current_site
     
    @@ -75,9 +77,10 @@ class UserCreationForm(forms.ModelForm):
                 'invalid': _("This value may contain only letters, numbers and "
                              "@/./+/-/_ characters.")})
         password1 = forms.CharField(label=_("Password"),
    -        widget=forms.PasswordInput)
    +        widget=forms.PasswordInput, max_length=MAXIMUM_PASSWORD_LENGTH)
         password2 = forms.CharField(label=_("Password confirmation"),
             widget=forms.PasswordInput,
    +        max_length=MAXIMUM_PASSWORD_LENGTH,
             help_text=_("Enter the same password as above, for verification."))
     
         class Meta:
    @@ -145,7 +148,11 @@ class AuthenticationForm(forms.Form):
         username/password logins.
         """
         username = forms.CharField(max_length=254)
    -    password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
    +    password = forms.CharField(
    +        label=_("Password"),
    +        widget=forms.PasswordInput,
    +        max_length=MAXIMUM_PASSWORD_LENGTH,
    +    )
     
         error_messages = {
             'invalid_login': _("Please enter a correct %(username)s and password. "
    @@ -269,10 +276,16 @@ class SetPasswordForm(forms.Form):
         error_messages = {
             'password_mismatch': _("The two password fields didn't match."),
         }
    -    new_password1 = forms.CharField(label=_("New password"),
    -                                    widget=forms.PasswordInput)
    -    new_password2 = forms.CharField(label=_("New password confirmation"),
    -                                    widget=forms.PasswordInput)
    +    new_password1 = forms.CharField(
    +        label=_("New password"),
    +        widget=forms.PasswordInput,
    +        max_length=MAXIMUM_PASSWORD_LENGTH,
    +    )
    +    new_password2 = forms.CharField(
    +        label=_("New password confirmation"),
    +        widget=forms.PasswordInput,
    +        max_length=MAXIMUM_PASSWORD_LENGTH,
    +    )
     
         def __init__(self, user, *args, **kwargs):
             self.user = user
    @@ -303,8 +316,11 @@ class PasswordChangeForm(SetPasswordForm):
             'password_incorrect': _("Your old password was entered incorrectly. "
                                     "Please enter it again."),
         })
    -    old_password = forms.CharField(label=_("Old password"),
    -                                   widget=forms.PasswordInput)
    +    old_password = forms.CharField(
    +        label=_("Old password"),
    +        widget=forms.PasswordInput,
    +        max_length=MAXIMUM_PASSWORD_LENGTH,
    +    )
     
         def clean_old_password(self):
             """
    @@ -329,10 +345,16 @@ class AdminPasswordChangeForm(forms.Form):
         error_messages = {
             'password_mismatch': _("The two password fields didn't match."),
         }
    -    password1 = forms.CharField(label=_("Password"),
    -                                widget=forms.PasswordInput)
    -    password2 = forms.CharField(label=_("Password (again)"),
    -                                widget=forms.PasswordInput)
    +    password1 = forms.CharField(
    +        label=_("Password"),
    +        widget=forms.PasswordInput,
    +        max_length=MAXIMUM_PASSWORD_LENGTH,
    +    )
    +    password2 = forms.CharField(
    +        label=_("Password (again)"),
    +        widget=forms.PasswordInput,
    +        max_length=MAXIMUM_PASSWORD_LENGTH,
    +    )
     
         def __init__(self, user, *args, **kwargs):
             self.user = user
    
  • django/contrib/auth/hashers.py+28 1 modified
    @@ -1,6 +1,7 @@
     from __future__ import unicode_literals
     
     import base64
    +import functools
     import hashlib
     
     from django.dispatch import receiver
    @@ -16,6 +17,7 @@
     
     
     UNUSABLE_PASSWORD = '!'  # This will never be a valid encoded hash
    +MAXIMUM_PASSWORD_LENGTH = 4096  # The maximum length a password can be to prevent DoS
     HASHERS = None  # lazily loaded from PASSWORD_HASHERS
     PREFERRED_HASHER = None  # defaults to first item in PASSWORD_HASHERS
     
    @@ -27,6 +29,18 @@ def reset_hashers(**kwargs):
             PREFERRED_HASHER = None
     
     
    +def password_max_length(max_length):
    +    def inner(fn):
    +        @functools.wraps(fn)
    +        def wrapper(self, password, *args, **kwargs):
    +            if len(password) > max_length:
    +                raise ValueError("Invalid password; Must be less than or equal"
    +                                 " to %d bytes" % max_length)
    +            return fn(self, password, *args, **kwargs)
    +        return wrapper
    +    return inner
    +
    +
     def is_password_usable(encoded):
         if encoded is None or encoded == UNUSABLE_PASSWORD:
             return False
    @@ -225,6 +239,7 @@ class PBKDF2PasswordHasher(BasePasswordHasher):
         iterations = 10000
         digest = hashlib.sha256
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def encode(self, password, salt, iterations=None):
             assert password
             assert salt and '$' not in salt
    @@ -234,6 +249,7 @@ def encode(self, password, salt, iterations=None):
             hash = base64.b64encode(hash).decode('ascii').strip()
             return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash)
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def verify(self, password, encoded):
             algorithm, iterations, salt, hash = encoded.split('$', 3)
             assert algorithm == self.algorithm
    @@ -279,13 +295,15 @@ def salt(self):
             bcrypt = self._load_library()
             return bcrypt.gensalt(self.rounds)
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def encode(self, password, salt):
             bcrypt = self._load_library()
             # Need to reevaluate the force_bytes call once bcrypt is supported on
             # Python 3
             data = bcrypt.hashpw(force_bytes(password), salt)
             return "%s$%s" % (self.algorithm, data)
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def verify(self, password, encoded):
             algorithm, data = encoded.split('$', 1)
             assert algorithm == self.algorithm
    @@ -310,12 +328,14 @@ class SHA1PasswordHasher(BasePasswordHasher):
         """
         algorithm = "sha1"
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def encode(self, password, salt):
             assert password
             assert salt and '$' not in salt
             hash = hashlib.sha1(force_bytes(salt + password)).hexdigest()
             return "%s$%s$%s" % (self.algorithm, salt, hash)
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def verify(self, password, encoded):
             algorithm, salt, hash = encoded.split('$', 2)
             assert algorithm == self.algorithm
    @@ -338,12 +358,14 @@ class MD5PasswordHasher(BasePasswordHasher):
         """
         algorithm = "md5"
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def encode(self, password, salt):
             assert password
             assert salt and '$' not in salt
             hash = hashlib.md5(force_bytes(salt + password)).hexdigest()
             return "%s$%s$%s" % (self.algorithm, salt, hash)
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def verify(self, password, encoded):
             algorithm, salt, hash = encoded.split('$', 2)
             assert algorithm == self.algorithm
    @@ -374,11 +396,13 @@ class UnsaltedSHA1PasswordHasher(BasePasswordHasher):
         def salt(self):
             return ''
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def encode(self, password, salt):
             assert salt == ''
             hash = hashlib.sha1(force_bytes(password)).hexdigest()
             return 'sha1$$%s' % hash
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def verify(self, password, encoded):
             encoded_2 = self.encode(password, '')
             return constant_time_compare(encoded, encoded_2)
    @@ -408,10 +432,12 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher):
         def salt(self):
             return ''
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def encode(self, password, salt):
             assert salt == ''
             return hashlib.md5(force_bytes(password)).hexdigest()
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def verify(self, password, encoded):
             if len(encoded) == 37 and encoded.startswith('md5$$'):
                 encoded = encoded[5:]
    @@ -437,13 +463,15 @@ class CryptPasswordHasher(BasePasswordHasher):
         def salt(self):
             return get_random_string(2)
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def encode(self, password, salt):
             crypt = self._load_library()
             assert len(salt) == 2
             data = crypt.crypt(force_str(password), salt)
             # we don't need to store the salt, but Django used to do this
             return "%s$%s$%s" % (self.algorithm, '', data)
     
    +    @password_max_length(MAXIMUM_PASSWORD_LENGTH)
         def verify(self, password, encoded):
             crypt = self._load_library()
             algorithm, salt, data = encoded.split('$', 2)
    @@ -458,4 +486,3 @@ def safe_summary(self, encoded):
                 (_('salt'), salt),
                 (_('hash'), mask_hash(data, show=3)),
             ])
    -
    
  • django/contrib/auth/tests/hashers.py+71 1 modified
    @@ -4,7 +4,8 @@
     from django.conf.global_settings import PASSWORD_HASHERS as default_hashers
     from django.contrib.auth.hashers import (is_password_usable,
         check_password, make_password, PBKDF2PasswordHasher, load_hashers,
    -    PBKDF2SHA1PasswordHasher, get_hasher, identify_hasher, UNUSABLE_PASSWORD)
    +    PBKDF2SHA1PasswordHasher, get_hasher, identify_hasher, UNUSABLE_PASSWORD,
    +    MAXIMUM_PASSWORD_LENGTH, password_max_length)
     from django.utils import unittest
     from django.utils.unittest import skipUnless
     
    @@ -31,6 +32,12 @@ def test_simple(self):
             self.assertTrue(is_password_usable(encoded))
             self.assertTrue(check_password('lètmein', encoded))
             self.assertFalse(check_password('lètmeinz', encoded))
    +        # Long password
    +        self.assertRaises(
    +            ValueError,
    +            make_password,
    +            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
    +        )
     
         def test_pkbdf2(self):
             encoded = make_password('lètmein', 'seasalt', 'pbkdf2_sha256')
    @@ -40,6 +47,14 @@ def test_pkbdf2(self):
             self.assertTrue(check_password('lètmein', encoded))
             self.assertFalse(check_password('lètmeinz', encoded))
             self.assertEqual(identify_hasher(encoded).algorithm, "pbkdf2_sha256")
    +        # Long password
    +        self.assertRaises(
    +            ValueError,
    +            make_password,
    +            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
    +            "seasalt",
    +            "pbkdf2_sha256",
    +        )
     
         def test_sha1(self):
             encoded = make_password('lètmein', 'seasalt', 'sha1')
    @@ -49,6 +64,14 @@ def test_sha1(self):
             self.assertTrue(check_password('lètmein', encoded))
             self.assertFalse(check_password('lètmeinz', encoded))
             self.assertEqual(identify_hasher(encoded).algorithm, "sha1")
    +        # Long password
    +        self.assertRaises(
    +            ValueError,
    +            make_password,
    +            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
    +            "seasalt",
    +            "sha1",
    +        )
     
         def test_md5(self):
             encoded = make_password('lètmein', 'seasalt', 'md5')
    @@ -58,6 +81,14 @@ def test_md5(self):
             self.assertTrue(check_password('lètmein', encoded))
             self.assertFalse(check_password('lètmeinz', encoded))
             self.assertEqual(identify_hasher(encoded).algorithm, "md5")
    +        # Long password
    +        self.assertRaises(
    +            ValueError,
    +            make_password,
    +            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
    +            "seasalt",
    +            "md5",
    +        )
     
         def test_unsalted_md5(self):
             encoded = make_password('lètmein', '', 'unsalted_md5')
    @@ -71,6 +102,14 @@ def test_unsalted_md5(self):
             self.assertTrue(is_password_usable(alt_encoded))
             self.assertTrue(check_password('lètmein', alt_encoded))
             self.assertFalse(check_password('lètmeinz', alt_encoded))
    +        # Long password
    +        self.assertRaises(
    +            ValueError,
    +            make_password,
    +            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
    +            "",
    +            "unsalted_md5",
    +        )
     
         def test_unsalted_sha1(self):
             encoded = make_password('lètmein', '', 'unsalted_sha1')
    @@ -82,6 +121,14 @@ def test_unsalted_sha1(self):
             # Raw SHA1 isn't acceptable
             alt_encoded = encoded[6:]
             self.assertFalse(check_password('lètmein', alt_encoded))
    +        # Long password
    +        self.assertRaises(
    +            ValueError,
    +            make_password,
    +            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
    +            "",
    +            "unslated_sha1",
    +        )
     
         @skipUnless(crypt, "no crypt module to generate password.")
         def test_crypt(self):
    @@ -91,6 +138,14 @@ def test_crypt(self):
             self.assertTrue(check_password('lètmei', encoded))
             self.assertFalse(check_password('lètmeiz', encoded))
             self.assertEqual(identify_hasher(encoded).algorithm, "crypt")
    +        # Long password
    +        self.assertRaises(
    +            ValueError,
    +            make_password,
    +            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
    +            "seasalt",
    +            "crypt",
    +        )
     
         @skipUnless(bcrypt, "py-bcrypt not installed")
         def test_bcrypt(self):
    @@ -100,6 +155,13 @@ def test_bcrypt(self):
             self.assertTrue(check_password('lètmein', encoded))
             self.assertFalse(check_password('lètmeinz', encoded))
             self.assertEqual(identify_hasher(encoded).algorithm, "bcrypt")
    +        # Long password
    +        self.assertRaises(
    +            ValueError,
    +            make_password,
    +            b"1" * (MAXIMUM_PASSWORD_LENGTH + 1),
    +            hasher="bcrypt",
    +        )
     
         def test_unusable(self):
             encoded = make_password(None)
    @@ -121,6 +183,14 @@ def test_bad_encoded(self):
             self.assertFalse(is_password_usable('lètmein_badencoded'))
             self.assertFalse(is_password_usable(''))
     
    +    def test_max_password_length_decorator(self):
    +        @password_max_length(10)
    +        def encode(s, password, salt):
    +            return True
    +
    +        self.assertTrue(encode(None, b"1234", b"1234"))
    +        self.assertRaises(ValueError, encode, None, b"1234567890A", b"1234")
    +
         def test_low_level_pkbdf2(self):
             hasher = PBKDF2PasswordHasher()
             encoded = hasher.encode('lètmein', 'seasalt')
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

11

News mentions

0

No linked articles in our index yet.