Skip to content

Commit bf39978

Browse files
committed
Fixed CVE-2018-16984 -- Fixed password hash disclosure to admin "view only" users.
Thanks Claude Paroz & Tim Graham for collaborating on the patch.
1 parent a4932be commit bf39978

File tree

4 files changed

+44
-3
lines changed

4 files changed

+44
-3
lines changed

django/contrib/admin/helpers.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,12 @@ def contents(self):
197197
except (AttributeError, ValueError, ObjectDoesNotExist):
198198
result_repr = self.empty_value_display
199199
else:
200+
if field in self.form.fields:
201+
widget = self.form[field].field.widget
202+
# This isn't elegant but suffices for contrib.auth's
203+
# ReadOnlyPasswordHashWidget.
204+
if getattr(widget, 'read_only', False):
205+
return widget.render(field, value)
200206
if f is None:
201207
if getattr(attr, 'boolean', False):
202208
result_repr = _boolean_icon(value)

django/contrib/auth/forms.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
class ReadOnlyPasswordHashWidget(forms.Widget):
2424
template_name = 'auth/widgets/read_only_password_hash.html'
25+
read_only = True
2526

2627
def get_context(self, name, value, attrs):
2728
context = super().get_context(name, value, attrs)

docs/releases/2.1.2.txt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,17 @@ Django 2.1.2 release notes
44

55
*Expected October 1, 2018*
66

7-
Django 2.1.2 fixes several bugs in 2.1.1. Also, the latest string translations
8-
from Transifex are incorporated.
7+
Django 2.1.2 fixes a security issue and several bugs in 2.1.1. Also, the latest
8+
string translations from Transifex are incorporated.
9+
10+
CVE-2018-16984: Password hash disclosure to "view only" admin users
11+
===================================================================
12+
13+
If an admin user has the change permission to the user model, only part of the
14+
password hash is displayed in the change form. Admin users with the view (but
15+
not change) permission to the user model were displayed the entire hash. While
16+
it's typically infeasible to reverse a strong password hash, if your site uses
17+
weaker password hashing algorithms such as MD5 or SHA1, it could be a problem.
918

1019
Bugfixes
1120
========

tests/auth_tests/test_views.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@
1515
from django.contrib.auth.forms import (
1616
AuthenticationForm, PasswordChangeForm, SetPasswordForm,
1717
)
18-
from django.contrib.auth.models import User
18+
from django.contrib.auth.models import Permission, User
1919
from django.contrib.auth.views import (
2020
INTERNAL_RESET_SESSION_TOKEN, LoginView, logout_then_login,
2121
redirect_to_login,
2222
)
23+
from django.contrib.contenttypes.models import ContentType
2324
from django.contrib.sessions.middleware import SessionMiddleware
2425
from django.contrib.sites.requests import RequestSite
2526
from django.core import mail
@@ -1098,6 +1099,11 @@ def test_logout_redirect_url_named_setting(self):
10981099
self.assertRedirects(response, '/logout/', fetch_redirect_response=False)
10991100

11001101

1102+
def get_perm(Model, perm):
1103+
ct = ContentType.objects.get_for_model(Model)
1104+
return Permission.objects.get(content_type=ct, codename=perm)
1105+
1106+
11011107
# Redirect in test_user_change_password will fail if session auth hash
11021108
# isn't updated after password change (#21649)
11031109
@override_settings(ROOT_URLCONF='auth_tests.urls_admin')
@@ -1211,6 +1217,25 @@ def test_user_change_password_passes_user_to_has_change_permission(self, has_cha
12111217
(_request, user), _kwargs = has_change_permission.call_args
12121218
self.assertEqual(user.pk, self.admin.pk)
12131219

1220+
def test_view_user_password_is_readonly(self):
1221+
u = User.objects.get(username='testclient')
1222+
u.is_superuser = False
1223+
u.save()
1224+
u.user_permissions.add(get_perm(User, 'view_user'))
1225+
response = self.client.get(reverse('auth_test_admin:auth_user_change', args=(u.pk,)),)
1226+
algo, salt, hash_string = (u.password.split('$'))
1227+
self.assertContains(response, '<div class="readonly">testclient</div>')
1228+
# ReadOnlyPasswordHashWidget is used to render the field.
1229+
self.assertContains(
1230+
response,
1231+
'<strong>algorithm</strong>: %s\n\n'
1232+
'<strong>salt</strong>: %s**********\n\n'
1233+
'<strong>hash</strong>: %s**************************\n\n' % (
1234+
algo, salt[:2], hash_string[:6],
1235+
),
1236+
html=True,
1237+
)
1238+
12141239

12151240
@override_settings(
12161241
AUTH_USER_MODEL='auth_tests.UUIDUser',

0 commit comments

Comments
 (0)