diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py
index 6fb35be1f3..5f5919d517 100644
--- a/django/contrib/admin/helpers.py
+++ b/django/contrib/admin/helpers.py
@@ -197,6 +197,12 @@ class AdminReadonlyField:
         except (AttributeError, ValueError, ObjectDoesNotExist):
             result_repr = self.empty_value_display
         else:
+            if field in self.form.fields:
+                widget = self.form[field].field.widget
+                # This isn't elegant but suffices for contrib.auth's
+                # ReadOnlyPasswordHashWidget.
+                if getattr(widget, 'read_only', False):
+                    return widget.render(field, value)
             if f is None:
                 if getattr(attr, 'boolean', False):
                     result_repr = _boolean_icon(value)
diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index dda6a07f02..472d2c5c8e 100644
--- a/django/contrib/auth/forms.py
+++ b/django/contrib/auth/forms.py
@@ -22,6 +22,7 @@ UserModel = get_user_model()
 
 class ReadOnlyPasswordHashWidget(forms.Widget):
     template_name = 'auth/widgets/read_only_password_hash.html'
+    read_only = True
 
     def get_context(self, name, value, attrs):
         context = super().get_context(name, value, attrs)
diff --git a/docs/releases/2.1.2.txt b/docs/releases/2.1.2.txt
index 55cc4bfca9..c0bcaf6b56 100644
--- a/docs/releases/2.1.2.txt
+++ b/docs/releases/2.1.2.txt
@@ -4,8 +4,17 @@ Django 2.1.2 release notes
 
 *Expected October 1, 2018*
 
-Django 2.1.2 fixes several bugs in 2.1.1. Also, the latest string translations
-from Transifex are incorporated.
+Django 2.1.2 fixes a security issue and several bugs in 2.1.1. Also, the latest
+string translations from Transifex are incorporated.
+
+CVE-2018-16984: Password hash disclosure to "view only" admin users
+===================================================================
+
+If an admin user has the change permission to the user model, only part of the
+password hash is displayed in the change form. Admin users with the view (but
+not change) permission to the user model were displayed the entire hash. While
+it's typically infeasible to reverse a strong password hash, if your site uses
+weaker password hashing algorithms such as MD5 or SHA1, it could be a problem.
 
 Bugfixes
 ========
diff --git a/tests/auth_tests/test_views.py b/tests/auth_tests/test_views.py
index 0facae74d4..f29f5f0949 100644
--- a/tests/auth_tests/test_views.py
+++ b/tests/auth_tests/test_views.py
@@ -15,11 +15,12 @@ from django.contrib.auth import (
 from django.contrib.auth.forms import (
     AuthenticationForm, PasswordChangeForm, SetPasswordForm,
 )
-from django.contrib.auth.models import User
+from django.contrib.auth.models import Permission, User
 from django.contrib.auth.views import (
     INTERNAL_RESET_SESSION_TOKEN, LoginView, logout_then_login,
     redirect_to_login,
 )
+from django.contrib.contenttypes.models import ContentType
 from django.contrib.sessions.middleware import SessionMiddleware
 from django.contrib.sites.requests import RequestSite
 from django.core import mail
@@ -1098,6 +1099,11 @@ class LogoutTest(AuthViewsTestCase):
         self.assertRedirects(response, '/logout/', fetch_redirect_response=False)
 
 
+def get_perm(Model, perm):
+    ct = ContentType.objects.get_for_model(Model)
+    return Permission.objects.get(content_type=ct, codename=perm)
+
+
 # Redirect in test_user_change_password will fail if session auth hash
 # isn't updated after password change (#21649)
 @override_settings(ROOT_URLCONF='auth_tests.urls_admin')
@@ -1211,6 +1217,25 @@ class ChangelistTests(AuthViewsTestCase):
         (_request, user), _kwargs = has_change_permission.call_args
         self.assertEqual(user.pk, self.admin.pk)
 
+    def test_view_user_password_is_readonly(self):
+        u = User.objects.get(username='testclient')
+        u.is_superuser = False
+        u.save()
+        u.user_permissions.add(get_perm(User, 'view_user'))
+        response = self.client.get(reverse('auth_test_admin:auth_user_change', args=(u.pk,)),)
+        algo, salt, hash_string = (u.password.split('$'))
+        self.assertContains(response, '<div class="readonly">testclient</div>')
+        # ReadOnlyPasswordHashWidget is used to render the field.
+        self.assertContains(
+            response,
+            '<strong>algorithm</strong>: %s\n\n'
+            '<strong>salt</strong>: %s**********\n\n'
+            '<strong>hash</strong>: %s**************************\n\n' % (
+                algo, salt[:2], hash_string[:6],
+            ),
+            html=True,
+        )
+
 
 @override_settings(
     AUTH_USER_MODEL='auth_tests.UUIDUser',