This article describes the Django vulnerability CVE-2019-19844, which was fixed on December 18, 2019.
Please refer to the following for release information on the official website.
Django security releases issued: 3.0.1, 2.2.9, and 1.11.27 | Weblog | Django
Affected Versions
The following versions are affected.
- Django master branch
- Django 3.0
- Django 2.2
- Django 1.11
Vulnerability Description(Potential Account Hijack via Password Reset Form)
By using the password reset function, an attacker can change the password of an existing user to an arbitrary value.
Proof of Concept
The following GitHub repository is prepared.
ryu22e/django_cve_2019_19844_poc
The following environment is required to run the above application.
- PostgreSQL 9.5 or higher
- Python 3.7.x
It is made on the following assumptions.
- Existing users
- Username:
mike123
, Email:mike@example.org
- Username:
- Email address of attacker
mıke@example.org
1
Follow the “Setup” in the README to create an existing user.
Next, reset the password according to “Procedure For Reproducing” in the README.
The password reset password of the mike123
user was sent to the email address mıke@example.org
prepared by the attacker, and from that the password of the mike123
user could have been changed without permission.
An email will be sent to mıke@example.org
(Email address of attacker), and you must have been able to change the mike123
user’s password without permission.
Which Code Was Wrong and How It Was Fixed
See the GitHub code below for the actual changes.
First, let’s check the code before modification. The flow of sending a password reset email from the django.contrib.auth.forms.PasswordResetForm.save
method is as follows.
- Check if the email address entered from the form exists in the database
- Send email to the email address entered from the form (if data exists in
1.
)
The code corresponding to 1.
is quoted below.
|
|
At first glance it looks fine, but the trap lies in the fact that iexact
is case insensitive.
After completing the above ryu22e/django_cve_2019_19844_poc “Setup”, you can see by executing the following code on the shell
command.
|
|
Originally, mike@example.org
and mıke@example.org
are different email addresses, but iexact
will find the corresponding data because i
and ı
are judged to be the same character.
In addition, the mail is sent to the mail address mıke@example.org
that does not exist in the database. because “Send email to the email address entered from the form” (in Section 2.
).
There have been two changes to these issues. The description in the official website release note “To resolve this, two changes were made in Django:” is quoted below.
- After retrieving a list of potentially-matching accounts from the database, Django’s password reset functionality now also checks the email address for equivalence in Python, using the recommended identifier-comparison process from Unicode Technical Report 36, section 2.11.2(B)(2).
- When generating password-reset emails, Django now sends to the email address retrieved from the database, rather than the email address submitted in the password-reset request form.
I will explain in order.
- After retrieving a list of …
The email address obtained from the database is compared with the email address entered from the form, and those with mismatched values are ignored.
To compare the values, use the method recommended by “Unicode Technical Report 36, section 2.11.2 (B) (2).” To convert the character string normalized by unicodedata.normalize and lowercased by str.casefold. It is targeted.
This allows you to remove unexpected values from iexact
, while retaining the ability to enter email addresses without regard to case.
- When generating password-reset emails, …
Emails are now sent to the database entry, not the email address entered in the form. As a result, even if there is a bug in 1.
, the attacker will not receive an email.