+92
-8
lines changedFilter options
+92
-8
lines changed Original file line number Diff line number Diff line change
@@ -20,6 +20,15 @@
20
20
UserModel = get_user_model()
21
21
22
22
23
+
def _unicode_ci_compare(s1, s2):
24
+
"""
25
+
Perform case-insensitive comparison of two identifiers, using the
26
+
recommended algorithm from Unicode Technical Report 36, section
27
+
2.11.2(B)(2).
28
+
"""
29
+
return unicodedata.normalize('NFKC', s1).casefold() == unicodedata.normalize('NFKC', s2).casefold()
30
+
31
+
23
32
class ReadOnlyPasswordHashWidget(forms.Widget):
24
33
template_name = 'auth/widgets/read_only_password_hash.html'
25
34
read_only = True
@@ -256,11 +265,16 @@ def get_users(self, email):
256
265
that prevent inactive users and users with unusable passwords from
257
266
resetting their password.
258
267
"""
268
+
email_field_name = UserModel.get_email_field_name()
259
269
active_users = UserModel._default_manager.filter(**{
260
-
'%s__iexact' % UserModel.get_email_field_name(): email,
270
+
'%s__iexact' % email_field_name: email,
261
271
'is_active': True,
262
272
})
263
-
return (u for u in active_users if u.has_usable_password())
273
+
return (
274
+
u for u in active_users
275
+
if u.has_usable_password() and
276
+
_unicode_ci_compare(email, getattr(u, email_field_name))
277
+
)
264
278
265
279
def save(self, domain_override=None,
266
280
subject_template_name='registration/password_reset_subject.txt',
@@ -273,15 +287,17 @@ def save(self, domain_override=None,
273
287
user.
274
288
"""
275
289
email = self.cleaned_data["email"]
290
+
email_field_name = UserModel.get_email_field_name()
276
291
for user in self.get_users(email):
277
292
if not domain_override:
278
293
current_site = get_current_site(request)
279
294
site_name = current_site.name
280
295
domain = current_site.domain
281
296
else:
282
297
site_name = domain = domain_override
298
+
user_email = getattr(user, email_field_name)
283
299
context = {
284
-
'email': email,
300
+
'email': user_email,
285
301
'domain': domain,
286
302
'site_name': site_name,
287
303
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
@@ -292,7 +308,7 @@ def save(self, domain_override=None,
292
308
}
293
309
self.send_mail(
294
310
subject_template_name, email_template_name, context, from_email,
295
-
email, html_email_template_name=html_email_template_name,
311
+
user_email, html_email_template_name=html_email_template_name,
296
312
)
297
313
298
314
Original file line number Diff line number Diff line change
@@ -2,9 +2,25 @@
2
2
Django 1.11.27 release notes
3
3
============================
4
4
5
-
*Expected January 2, 2020*
5
+
*December 18, 2019*
6
6
7
-
Django 1.11.27 fixes a data loss bug in 1.11.26.
7
+
Django 1.11.27 fixes a security issue and a data loss bug in 1.11.26.
8
+
9
+
CVE-2019-19844: Potential account hijack via password reset form
10
+
================================================================
11
+
12
+
By submitting a suitably crafted email address making use of Unicode
13
+
characters, that compared equal to an existing user email when lower-cased for
14
+
comparison, an attacker could be sent a password reset token for the matched
15
+
account.
16
+
17
+
In order to avoid this vulnerability, password reset requests now compare the
18
+
submitted email using the stricter, recommended algorithm for case-insensitive
19
+
comparison of two identifiers from `Unicode Technical Report 36, section
20
+
2.11.2(B)(2)`__. Upon a match, the email containing the reset token will be
21
+
sent to the email address on record rather than the submitted address.
22
+
23
+
.. __: https://www.unicode.org/reports/tr36/#Recommendations_General
8
24
9
25
Bugfixes
10
26
========
Original file line number Diff line number Diff line change
@@ -2,9 +2,25 @@
2
2
Django 2.2.9 release notes
3
3
==========================
4
4
5
-
*Expected January 2, 2020*
5
+
*December 18, 2019*
6
6
7
-
Django 2.2.9 fixes a data loss bug in 2.2.8.
7
+
Django 2.2.9 fixes a security issue and a data loss bug in 2.2.8.
8
+
9
+
CVE-2019-19844: Potential account hijack via password reset form
10
+
================================================================
11
+
12
+
By submitting a suitably crafted email address making use of Unicode
13
+
characters, that compared equal to an existing user email when lower-cased for
14
+
comparison, an attacker could be sent a password reset token for the matched
15
+
account.
16
+
17
+
In order to avoid this vulnerability, password reset requests now compare the
18
+
submitted email using the stricter, recommended algorithm for case-insensitive
19
+
comparison of two identifiers from `Unicode Technical Report 36, section
20
+
2.11.2(B)(2)`__. Upon a match, the email containing the reset token will be
21
+
sent to the email address on record rather than the submitted address.
22
+
23
+
.. __: https://www.unicode.org/reports/tr36/#Recommendations_General
8
24
9
25
Bugfixes
10
26
========
Original file line number Diff line number Diff line change
@@ -754,6 +754,42 @@ def test_invalid_email(self):
754
754
self.assertFalse(form.is_valid())
755
755
self.assertEqual(form['email'].errors, [_('Enter a valid email address.')])
756
756
757
+
def test_user_email_unicode_collision(self):
758
+
User.objects.create_user('mike123', 'mike@example.org', 'test123')
759
+
User.objects.create_user('mike456', 'mıke@example.org', 'test123')
760
+
data = {'email': 'mıke@example.org'}
761
+
form = PasswordResetForm(data)
762
+
self.assertTrue(form.is_valid())
763
+
form.save()
764
+
self.assertEqual(len(mail.outbox), 1)
765
+
self.assertEqual(mail.outbox[0].to, ['mıke@example.org'])
766
+
767
+
def test_user_email_domain_unicode_collision(self):
768
+
User.objects.create_user('mike123', 'mike@ixample.org', 'test123')
769
+
User.objects.create_user('mike456', 'mike@ıxample.org', 'test123')
770
+
data = {'email': 'mike@ıxample.org'}
771
+
form = PasswordResetForm(data)
772
+
self.assertTrue(form.is_valid())
773
+
form.save()
774
+
self.assertEqual(len(mail.outbox), 1)
775
+
self.assertEqual(mail.outbox[0].to, ['mike@ıxample.org'])
776
+
777
+
def test_user_email_unicode_collision_nonexistent(self):
778
+
User.objects.create_user('mike123', 'mike@example.org', 'test123')
779
+
data = {'email': 'mıke@example.org'}
780
+
form = PasswordResetForm(data)
781
+
self.assertTrue(form.is_valid())
782
+
form.save()
783
+
self.assertEqual(len(mail.outbox), 0)
784
+
785
+
def test_user_email_domain_unicode_collision_nonexistent(self):
786
+
User.objects.create_user('mike123', 'mike@ixample.org', 'test123')
787
+
data = {'email': 'mike@ıxample.org'}
788
+
form = PasswordResetForm(data)
789
+
self.assertTrue(form.is_valid())
790
+
form.save()
791
+
self.assertEqual(len(mail.outbox), 0)
792
+
757
793
def test_nonexistent_email(self):
758
794
"""
759
795
Test nonexistent email address. This should not fail because it would
You can’t perform that action at this time.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4