Opened 15 months ago

Closed 15 months ago

Last modified 15 months ago

#34661 closed New feature (duplicate)

Peppering user passwords

Reported by: Fatih Erikli Owned by: nobody
Component: contrib.auth Version: 4.2
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by Fatih Erikli)

Currently a user's password stored in database in this format:

<algorithm>$<iterations>$<salt>$<hash>

Hash (Last column) is the password hashed with the salt in previous column.

Example, these are two computed passwords, stored on database, for the same password of two users.

pbkdf2_sha256$600000$fb9cUsHWK4EMZ7VWGBAcGD$2uwiVefFwanIhxLrv+/t3sKvP4X6tDKEMw/ysHD5dIc=
pbkdf2_sha256$600000$HgWHWrF2qQD9Owj4XeEkjY$rh0qzfo+/ZCzWbL9ZJa8aKhiO5xoEMfT4EtP/+A+LzI=

The password is 123456.

Imagine I am an attacker, who got the database of different django project, I want to look up the users who have chosen the password 123456.

I have the salts of users stored in database.

make_password('123456', 'fb9cUsHWK4EMZ7VWGBAcGD')
pbkdf2_sha256$600000$fb9cUsHWK4EMZ7VWGBAcGD$2uwiVefFwanIhxLrv+/t3sKvP4X6tDKEMw/ysHD5dIc=

make_password('123456', 'HgWHWrF2qQD9Owj4XeEkjY')
pbkdf2_sha256$600000$HgWHWrF2qQD9Owj4XeEkjY$rh0qzfo+/ZCzWbL9ZJa8aKhiO5xoEMfT4EtP/+A+LzI=

These are correct password combinations. I am able to lookup the users who have their passwords exposed in public.

Password 123456 is not a possible case in Django, since the password fields have a complexity validation. However, the salt is available to the attacker when a database is stolen. Salt could be used

  • to hash the raw password pair in a rainbow table.
  • to hash the already exposed passwords.

There is one more element needed for hashing the password, pepper, should be project specific. When a database is exposed in public, the attacker will not be able to lookup the passwords, since they don't have the secret pepper key.

I am not sure about the vulnerability enumeration, but CWE-760 seems closer. Salt is not weak, but it is known. The salt is stored next to the hashed password.

This is a case of when a database is stolen, however I think Django, by default, should do everything that could be done at the framework level to keep the user information secured.

Change History (14)

comment:1 by Fatih Erikli, 15 months ago

Description: modified (diff)

comment:2 by Fatih Erikli, 15 months ago

Description: modified (diff)

comment:3 by Fatih Erikli, 15 months ago

Description: modified (diff)

comment:4 by Fatih Erikli, 15 months ago

Description: modified (diff)

comment:5 by Fatih Erikli, 15 months ago

Description: modified (diff)

comment:6 by Fatih Erikli, 15 months ago

Description: modified (diff)

comment:7 by Fatih Erikli, 15 months ago

Description: modified (diff)

comment:8 by Fatih Erikli, 15 months ago

Here is an example hasher:

# yourapp.hashers.py
class PepperedPBKDF2PasswordHasher(PBKDF2PasswordHasher):
    algorithm = "pepperred_pbkdf2_sha256"

    def encode(self, password, salt, iterations=None):
        iterations = iterations or self.iterations
        hash = pbkdf2(password, salt + settings.PASSWORD_PEPPER, iterations, digest=self.digest)
        hash = base64.b64encode(hash).decode('ascii').strip()
        return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash)

(There was an error in example solution. It's edited. I added the pepper to wrong place, to the hashed version before the base64 encoding, the pepper was visible when base64 decoded. Corrected it. If you applied this solution before please double check if it is added to the salt before the hash.)

In settings:

PASSWORD_PEPPER = b'4545randombytes342445'
PASSWORD_HASHERS = [
    "yourapp.hashers.PepperedPBKDF2PasswordHasher",
    "django.contrib.auth.hashers.PBKDF2PasswordHasher",
    "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
    "django.contrib.auth.hashers.Argon2PasswordHasher",
    "django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
    "django.contrib.auth.hashers.ScryptPasswordHasher",
]

Passwords will continue working when the last (first in the list) hasher is added. Django updates the computed passwords when a new hashing algorithm is added. The passwords will break when a pepper is changed.

Version 11, edited 15 months ago by Fatih Erikli (previous) (next) (diff)

comment:9 by Fatih Erikli, 15 months ago

Description: modified (diff)

comment:10 by Fatih Erikli, 15 months ago

Description: modified (diff)

comment:11 by Fatih Erikli, 15 months ago

Description: modified (diff)

comment:12 by Fatih Erikli, 15 months ago

Description: modified (diff)

comment:13 by Mariusz Felisiak, 15 months ago

Resolution: duplicate
Status: newclosed

Duplicate of #30561.

comment:14 by Mariusz Felisiak, 15 months ago

Type: UncategorizedNew feature
Note: See TracTickets for help on using tickets.
Back to Top