티스토리 수익 글 보기

티스토리 수익 글 보기

Fixed CVE-2024-45231 — Avoided server error on password reset when e… · django/django@8c35a0a · GitHub
Skip to content

Commit 8c35a0a

Browse files
committed
Fixed CVE-2024-45231 — Avoided server error on password reset when email sending fails.
On successful submission of a password reset request, an email is sent to the accounts known to the system. If sending this email fails (due to email backend misconfiguration, service provider outage, network issues, etc.), an attacker might exploit this by detecting which password reset requests succeed and which ones generate a 500 error response. Thanks to Thibaut Spriet for the report, and to Mariusz Felisiak, Adam Johnson, and Sarah Boyce for the reviews.
1 parent 320dd27 commit 8c35a0a

File tree

8 files changed

+83
2
lines changed

8 files changed

+83
2
lines changed

django/contrib/auth/forms.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import logging
12
import unicodedata
23

34
from django import forms
@@ -16,6 +17,7 @@
1617
from django.utils.translation import gettext_lazy as _
1718

1819
UserModel = get_user_model()
20+
logger = logging.getLogger("django.contrib.auth")
1921

2022

2123
def _unicode_ci_compare(s1, s2):
@@ -418,7 +420,12 @@ def send_mail(
418420
html_email = loader.render_to_string(html_email_template_name, context)
419421
email_message.attach_alternative(html_email, "text/html")
420422

421-
email_message.send()
423+
try:
424+
email_message.send()
425+
except Exception:
426+
logger.exception(
427+
"Failed to send password reset email to %s", context["user"].pk
428+
)
422429

423430
def get_users(self, email):
424431
"""Given an email, return matching user(s) who should receive a reset.

docs/ref/logging.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,18 @@ Django development server. This logger generates an ``INFO`` message upon
209209
detecting a modification in a source code file and may produce ``WARNING``
210210
messages during filesystem inspection and event subscription processes.
211211

212+
.. _django-contrib-auth-logger:
213+
214+
``django.contrib.auth``
215+
~~~~~~~~~~~~~~~~~~~~~~~
216+
217+
.. versionadded:: 4.2.16
218+
219+
Log messages related to :doc:`contrib/auth`, particularly ``ERROR`` messages
220+
are generated when a :class:`~django.contrib.auth.forms.PasswordResetForm` is
221+
successfully submitted but the password reset email cannot be delivered due to
222+
a mail sending exception.
223+
212224
.. _django-contrib-gis-logger:
213225

214226
``django.contrib.gis``

docs/releases/4.2.16.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,14 @@ CVE-2024-45230: Potential denial-of-service vulnerability in ``django.utils.html
1313
:tfilter:`urlize` and :tfilter:`urlizetrunc` were subject to a potential
1414
denial-of-service attack via very large inputs with a specific sequence of
1515
characters.
16+
17+
CVE-2024-45231: Potential user email enumeration via response status on password reset
18+
======================================================================================
19+
20+
Due to unhandled email sending failures, the
21+
:class:`~django.contrib.auth.forms.PasswordResetForm` class allowed remote
22+
attackers to enumerate user emails by issuing password reset requests and
23+
observing the outcomes.
24+
25+
To mitigate this risk, exceptions occurring during password reset email sending
26+
are now handled and logged using the :ref:`django-contrib-auth-logger` logger.

docs/releases/5.0.9.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,14 @@ CVE-2024-45230: Potential denial-of-service vulnerability in ``django.utils.html
1313
:tfilter:`urlize` and :tfilter:`urlizetrunc` were subject to a potential
1414
denial-of-service attack via very large inputs with a specific sequence of
1515
characters.
16+
17+
CVE-2024-45231: Potential user email enumeration via response status on password reset
18+
======================================================================================
19+
20+
Due to unhandled email sending failures, the
21+
:class:`~django.contrib.auth.forms.PasswordResetForm` class allowed remote
22+
attackers to enumerate user emails by issuing password reset requests and
23+
observing the outcomes.
24+
25+
To mitigate this risk, exceptions occurring during password reset email sending
26+
are now handled and logged using the :ref:`django-contrib-auth-logger` logger.

docs/releases/5.1.1.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@ CVE-2024-45230: Potential denial-of-service vulnerability in ``django.utils.html
1414
denial-of-service attack via very large inputs with a specific sequence of
1515
characters.
1616

17+
CVE-2024-45231: Potential user email enumeration via response status on password reset
18+
======================================================================================
19+
20+
Due to unhandled email sending failures, the
21+
:class:`~django.contrib.auth.forms.PasswordResetForm` class allowed remote
22+
attackers to enumerate user emails by issuing password reset requests and
23+
observing the outcomes.
24+
25+
To mitigate this risk, exceptions occurring during password reset email sending
26+
are now handled and logged using the :ref:`django-contrib-auth-logger` logger.
27+
1728
Bugfixes
1829
========
1930

docs/topics/auth/default.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1732,7 +1732,9 @@ provides several built-in forms located in :mod:`django.contrib.auth.forms`:
17321732
.. method:: send_mail(subject_template_name, email_template_name, context, from_email, to_email, html_email_template_name=None)
17331733

17341734
Uses the arguments to send an ``EmailMultiAlternatives``.
1735-
Can be overridden to customize how the email is sent to the user.
1735+
Can be overridden to customize how the email is sent to the user. If
1736+
you choose to override this method, be mindful of handling potential
1737+
exceptions raised due to email sending failures.
17361738

17371739
:param subject_template_name: the template for the subject.
17381740
:param email_template_name: the template for the email body.

tests/auth_tests/test_forms.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1357,6 +1357,27 @@ def test_save_html_email_template_name(self):
13571357
)
13581358
)
13591359

1360+
@override_settings(EMAIL_BACKEND="mail.custombackend.FailingEmailBackend")
1361+
def test_save_send_email_exceptions_are_catched_and_logged(self):
1362+
(user, username, email) = self.create_dummy_user()
1363+
form = PasswordResetForm({"email": email})
1364+
self.assertTrue(form.is_valid())
1365+
1366+
with self.assertLogs("django.contrib.auth", level=0) as cm:
1367+
form.save()
1368+
1369+
self.assertEqual(len(mail.outbox), 0)
1370+
self.assertEqual(len(cm.output), 1)
1371+
errors = cm.output[0].split("\n")
1372+
pk = user.pk
1373+
self.assertEqual(
1374+
errors[0],
1375+
f"ERROR:django.contrib.auth:Failed to send password reset email to {pk}",
1376+
)
1377+
self.assertEqual(
1378+
errors[-1], "ValueError: FailingEmailBackend is doomed to fail."
1379+
)
1380+
13601381
@override_settings(AUTH_USER_MODEL="auth_tests.CustomEmailField")
13611382
def test_custom_email_field(self):
13621383
email = "test@mail.com"

tests/mail/custombackend.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,9 @@ def send_messages(self, email_messages):
1212
# Messages are stored in an instance variable for testing.
1313
self.test_outbox.extend(email_messages)
1414
return len(email_messages)
15+
16+
17+
class FailingEmailBackend(BaseEmailBackend):
18+
19+
def send_messages(self, email_messages):
20+
raise ValueError("FailingEmailBackend is doomed to fail.")

0 commit comments

Comments
 (0)