[Django] #34661: Peppering user passwords

47 views
Skip to first unread message

Django

unread,
Jun 16, 2023, 12:54:58 PM6/16/23
to django-...@googlegroups.com
#34661: Peppering user passwords
-----------------------------------------+------------------------
Reporter: Fatih Erikli | Owner: nobody
Type: Uncategorized | Status: new
Component: contrib.auth | Version: 4.2
Severity: Normal | Keywords:
Triage Stage: Unreviewed | Has patch: 0
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
-----------------------------------------+------------------------
Currently a user's password stored in database in this format:

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

The salt is a random generated string per user. Hash (Last column) is the
password hashed with the stored 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 the 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.

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

Is this a known issue?

--
Ticket URL: <https://code.djangoproject.com/ticket/34661>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Jun 16, 2023, 1:15:26 PM6/16/23
to django-...@googlegroups.com
#34661: Peppering user passwords
-------------------------------+--------------------------------------

Reporter: Fatih Erikli | Owner: nobody
Type: Uncategorized | Status: new
Component: contrib.auth | Version: 4.2
Severity: Normal | Resolution:

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------+--------------------------------------
Description changed by Fatih Erikli:

Old description:

> Currently a user's password stored in database in this format:
>
> > <algorithm>$<iterations>$<salt>$<hash>
>
> The salt is a random generated string per user. Hash (Last column) is the
> password hashed with the stored 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 the 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.
>
> There is one more element needed for hashing the password, **pepper**,
> should be django project specific. Even when a database is exposed, the
> attacker will not be able to lookup the known passwords, since they don't
> have the secret pepper key.
>
> Is this a known issue?

New description:

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

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

The salt is a random generated string per user. Hash (Last column) is the
password hashed with the stored 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 the 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.

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

This causes CWE-760: Use of a One-Way Hash with a Predictable Salt
[https://cwe.mitre.org/data/definitions/760.html].

--

--
Ticket URL: <https://code.djangoproject.com/ticket/34661#comment:1>

Django

unread,
Jun 16, 2023, 1:18:36 PM6/16/23
to django-...@googlegroups.com

Old description:

New description:

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

The password is **123456**.

--

--
Ticket URL: <https://code.djangoproject.com/ticket/34661#comment:2>

Django

unread,
Jun 16, 2023, 1:20:48 PM6/16/23
to django-...@googlegroups.com

Old description:

New description:

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

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

The salt is a random generated string per user. Hash (Last column) is the
password hashed with the stored 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 the 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.

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

I am not sure, however this cause CWE-760
https://cwe.mitre.org/data/definitions/760.html, even though salt is not
weak, but it is known, when a database is exposed.

I think peppering passwords should be a default behavior of django.

--

--
Ticket URL: <https://code.djangoproject.com/ticket/34661#comment:3>

Django

unread,
Jun 16, 2023, 1:22:33 PM6/16/23
to django-...@googlegroups.com

Old description:

New description:

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

The password is **123456**.

I am not sure about the vulnerability enumeration, however this cause
CWE-760 [0] even though salt is not weak, but it is known, when a database
is exposed. Because the salt is stored next to the hashed password.

I think peppering passwords should be a default behavior of django.

[0] [https://cwe.mitre.org/data/definitions/760.html,]

--

--
Ticket URL: <https://code.djangoproject.com/ticket/34661#comment:4>

Django

unread,
Jun 16, 2023, 1:22:56 PM6/16/23
to django-...@googlegroups.com

Old description:

New description:

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

The password is **123456**.

CWE-760 even though salt is not weak, but it is known, when a database is


exposed. Because the salt is stored next to the hashed password.

I think peppering passwords should be a default behavior of django.

--

--
Ticket URL: <https://code.djangoproject.com/ticket/34661#comment:5>

Django

unread,
Jun 16, 2023, 1:23:32 PM6/16/23
to django-...@googlegroups.com

Old description:

> CWE-760 even though salt is not weak, but it is known, when a database is


> exposed. Because the salt is stored next to the hashed password.
>
> I think peppering passwords should be a default behavior of django.

New description:

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

The password is **123456**.

I think peppering passwords should be default behavior of django.

--

--
Ticket URL: <https://code.djangoproject.com/ticket/34661#comment:6>

Django

unread,
Jun 16, 2023, 1:49:22 PM6/16/23
to django-...@googlegroups.com

Old description:

> I think peppering passwords should be default behavior of django.

New description:

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

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

The salt is a random generated string per user. Hash (Last column) is the
password hashed with the stored 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.

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

I am not sure about the vulnerability enumeration, however this cause
CWE-760 even though salt is not weak, but it is known, when a database is
exposed. Because the salt is stored next to the hashed password.

I think peppering passwords should be default behavior of django.

--

--
Ticket URL: <https://code.djangoproject.com/ticket/34661#comment:7>

Django

unread,
Jun 17, 2023, 7:53:02 AM6/17/23
to django-...@googlegroups.com
#34661: Peppering user passwords
-------------------------------+--------------------------------------
Reporter: Fatih Erikli | Owner: nobody
Type: Uncategorized | Status: new
Component: contrib.auth | Version: 4.2
Severity: Normal | Resolution:
Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------+--------------------------------------

Comment (by Fatih Erikli):

Here is an example hasher:
{{{
# yourapp.hashers.py
class PepperedPBKDF2PasswordHasher(PBKDF2PasswordHasher):
algorithm = "pepperred_pbkdf2_sha256"

def encode(self, password, salt, iterations=None):
assert password is not None
assert salt and '$' not in salt
iterations = iterations or self.iterations
hash = pbkdf2(password, salt, iterations, digest=self.digest)
hash = base64.b64encode(
hash+settings.PASSWORD_PEPPER).decode('ascii').strip()
return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, 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",
]
}}}

The passwords will break when a pepper is changed. All the peppers should
be archived with the associated users in case if a pepper secret key is
exposed to the public. The previous peppers will be needed for logging the
affected users in (before sending a password change notice, for example).

--
Ticket URL: <https://code.djangoproject.com/ticket/34661#comment:8>

Django

unread,
Jun 18, 2023, 6:16:03 PM6/18/23
to django-...@googlegroups.com
#34661: Peppering user passwords
-------------------------------+--------------------------------------
Reporter: Fatih Erikli | Owner: nobody
Type: Uncategorized | Status: new
Component: contrib.auth | Version: 4.2
Severity: Normal | Resolution:
Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------+--------------------------------------
Description changed by Fatih Erikli:

Old description:

> Currently a user's password stored in database in this format:
>
> > <algorithm>$<iterations>$<salt>$<hash>
>
> The salt is a random generated string per user. Hash (Last column) is the
> password hashed with the stored 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.

New description:

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

The password is **123456**.

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 django project specific. Even when a database is exposed, the
attacker will not be able to lookup the known passwords, since they don't
have the secret pepper key.

I am not sure about the vulnerability enumeration, however this cause
CWE-760 even though salt is not weak, but it is known, when a database is
exposed. Because 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.

--

--
Ticket URL: <https://code.djangoproject.com/ticket/34661#comment:9>

Django

unread,
Jun 18, 2023, 6:20:21 PM6/18/23
to django-...@googlegroups.com

Old description:

New description:

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

The password is **123456**.

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.

--

--
Ticket URL: <https://code.djangoproject.com/ticket/34661#comment:10>

Django

unread,
Jun 18, 2023, 8:43:38 PM6/18/23
to django-...@googlegroups.com

Old description:

> 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.

New description:

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

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

The salt is a random generated string per user. Hash (Last column) is the

password hashed with the stored value in previous column.

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

The password is **123456**.

--

--
Ticket URL: <https://code.djangoproject.com/ticket/34661#comment:11>

Django

unread,
Jun 18, 2023, 8:44:30 PM6/18/23
to django-...@googlegroups.com
#34661: Peppering user passwords
-------------------------------+--------------------------------------
Reporter: Fatih Erikli | Owner: nobody
Type: Uncategorized | Status: new
Component: contrib.auth | Version: 4.2
Severity: Normal | Resolution:
Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------+--------------------------------------
Description changed by Fatih Erikli:

Old description:

> Currently a user's password stored in database in this format:
>
> > <algorithm>$<iterations>$<salt>$<hash>
>
> The salt is a random generated string per user. Hash (Last column) is the

New description:

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.

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

The password is **123456**.

--

--
Ticket URL: <https://code.djangoproject.com/ticket/34661#comment:12>

Django

unread,
Jun 19, 2023, 7:15:39 AM6/19/23
to django-...@googlegroups.com
#34661: Peppering user passwords
-------------------------------+--------------------------------------
Reporter: Fatih Erikli | Owner: nobody
Type: Uncategorized | Status: closed
Component: contrib.auth | Version: 4.2
Severity: Normal | Resolution: duplicate

Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------+--------------------------------------
Changes (by Mariusz Felisiak):

* status: new => closed
* resolution: => duplicate


Comment:

Duplicate of #30561.

--
Ticket URL: <https://code.djangoproject.com/ticket/34661#comment:13>

Django

unread,
Jun 19, 2023, 7:18:28 AM6/19/23
to django-...@googlegroups.com
#34661: Peppering user passwords

------------------------------+--------------------------------------
Reporter: Fatih Erikli | Owner: nobody
Type: New feature | Status: closed

Component: contrib.auth | Version: 4.2
Severity: Normal | Resolution: duplicate
Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
------------------------------+--------------------------------------
Changes (by Mariusz Felisiak):

* type: Uncategorized => New feature


--
Ticket URL: <https://code.djangoproject.com/ticket/34661#comment:14>

Reply all
Reply to author
Forward
0 new messages