summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Graham <timograham@gmail.com>2018-01-23 13:20:18 -0500
committerTim Graham <timograham@gmail.com>2018-02-01 09:18:33 -0500
commit57b95fedad5e0b83fc9c81466b7d1751c6427aae (patch)
tree1f6f1066fc8aa01e89124875bdb9dd62073bc9c4
parent1c9233b1b9f903e4e2cb20a724e8c22aee4aacb2 (diff)
downloaddjango-57b95fedad5e0b83fc9c81466b7d1751c6427aae.tar.gz
[1.11.x] Fixed CVE-2018-6188 -- Fixed information leakage in AuthenticationForm.
Reverted 359370a8b8ca0efe99b1d4630b291ec060b69225 (refs #28645). This is a security fix.
-rw-r--r--django/contrib/auth/forms.py9
-rw-r--r--docs/releases/1.11.10.txt23
-rw-r--r--tests/auth_tests/test_forms.py21
3 files changed, 42 insertions, 11 deletions
diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index abfd96aeec..02250d83da 100644
--- a/django/contrib/auth/forms.py
+++ b/django/contrib/auth/forms.py
@@ -186,15 +186,6 @@ class AuthenticationForm(forms.Form):
if username is not None and password:
self.user_cache = authenticate(self.request, username=username, password=password)
if self.user_cache is None:
- # An authentication backend may reject inactive users. Check
- # if the user exists and is inactive, and raise the 'inactive'
- # error if so.
- try:
- self.user_cache = UserModel._default_manager.get_by_natural_key(username)
- except UserModel.DoesNotExist:
- pass
- else:
- self.confirm_login_allowed(self.user_cache)
raise forms.ValidationError(
self.error_messages['invalid_login'],
code='invalid_login',
diff --git a/docs/releases/1.11.10.txt b/docs/releases/1.11.10.txt
index cfa8fc2070..96f07920a3 100644
--- a/docs/releases/1.11.10.txt
+++ b/docs/releases/1.11.10.txt
@@ -2,9 +2,28 @@
Django 1.11.10 release notes
============================
-*Expected February 1, 2018*
+*February 1, 2018*
-Django 1.11.10 fixes several bugs in 1.11.9.
+Django 1.11.10 fixes a security issue and several bugs in 1.11.9.
+
+CVE-2018-6188: Information leakage in ``AuthenticationForm``
+============================================================
+
+A regression in Django 1.11.8 made
+:class:`~django.contrib.auth.forms.AuthenticationForm` run its
+``confirm_login_allowed()`` method even if an incorrect password is entered.
+This can leak information about a user, depending on what messages
+``confirm_login_allowed()`` raises. If ``confirm_login_allowed()`` isn't
+overridden, an attacker enter an arbitrary username and see if that user has
+been set to ``is_active=False``. If ``confirm_login_allowed()`` is overridden,
+more sensitive details could be leaked.
+
+This issue is fixed with the caveat that ``AuthenticationForm`` can no longer
+raise the "This account is inactive." error if the authentication backend
+rejects inactive users (the default authentication backend, ``ModelBackend``,
+has done that since Django 1.10). This issue will be revisited for Django 2.1
+as a fix to address the caveat will likely be too invasive for inclusion in
+older versions.
Bugfixes
========
diff --git a/tests/auth_tests/test_forms.py b/tests/auth_tests/test_forms.py
index b82bbc58f4..e09285277f 100644
--- a/tests/auth_tests/test_forms.py
+++ b/tests/auth_tests/test_forms.py
@@ -249,6 +249,9 @@ class UserCreationFormTest(TestDataMixin, TestCase):
)
+# To verify that the login form rejects inactive users, use an authentication
+# backend that allows them.
+@override_settings(AUTHENTICATION_BACKENDS=['django.contrib.auth.backends.AllowAllUsersModelBackend'])
class AuthenticationFormTest(TestDataMixin, TestCase):
def test_invalid_username(self):
@@ -278,6 +281,24 @@ class AuthenticationFormTest(TestDataMixin, TestCase):
self.assertFalse(form.is_valid())
self.assertEqual(form.non_field_errors(), [force_text(form.error_messages['inactive'])])
+ # Use an authentication backend that rejects inactive users.
+ @override_settings(AUTHENTICATION_BACKENDS=['django.contrib.auth.backends.ModelBackend'])
+ def test_inactive_user_incorrect_password(self):
+ """An invalid login doesn't leak the inactive status of a user."""
+ data = {
+ 'username': 'inactive',
+ 'password': 'incorrect',
+ }
+ form = AuthenticationForm(None, data)
+ self.assertFalse(form.is_valid())
+ self.assertEqual(
+ form.non_field_errors(), [
+ form.error_messages['invalid_login'] % {
+ 'username': User._meta.get_field('username').verbose_name
+ }
+ ]
+ )
+
def test_login_failed(self):
signal_calls = []