티스토리 수익 글 보기

티스토리 수익 글 보기

[1.4.x] Prevented data leakage in contrib.admin via query string mani… · django/django@027bd34 · GitHub
Skip to content
/ django Public

Commit 027bd34

Browse files
charettestimgraham
authored andcommitted
[1.4.x] Prevented data leakage in contrib.admin via query string manipulation.
This is a security fix. Disclosure following shortly.
1 parent c9e3b99 commit 027bd34

File tree

5 files changed

+61
5
lines changed

5 files changed

+61
5
lines changed

django/contrib/admin/exceptions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from django.core.exceptions import SuspiciousOperation
2+
3+
4+
class DisallowedModelAdminToField(SuspiciousOperation):
5+
"""Invalid to_field was passed to admin view via URL query string"""
6+
pass

django/contrib/admin/options.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,24 @@ def lookup_allowed(self, lookup, value):
269269
clean_lookup = LOOKUP_SEP.join(parts)
270270
return clean_lookup in self.list_filter or clean_lookup == self.date_hierarchy
271271

272+
def to_field_allowed(self, request, to_field):
273+
opts = self.model._meta
274+
275+
try:
276+
field = opts.get_field(to_field)
277+
except FieldDoesNotExist:
278+
return False
279+
280+
# Make sure at least one of the models registered for this site
281+
# references this field.
282+
registered_models = self.admin_site._registry
283+
for related_object in opts.get_all_related_objects():
284+
if (related_object.model in registered_models and
285+
field == related_object.field.rel.get_related_field()):
286+
return True
287+
288+
return False
289+
272290
def has_add_permission(self, request):
273291
"""
274292
Returns True if the given request has permission to add an object.

django/contrib/admin/views/main.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from django.utils.http import urlencode
1111

1212
from django.contrib.admin import FieldListFilter
13+
from django.contrib.admin.exceptions import DisallowedModelAdminToField
1314
from django.contrib.admin.options import IncorrectLookupParameters
1415
from django.contrib.admin.util import (quote, get_fields_from_path,
1516
lookup_needs_distinct, prepare_lookup_value)
@@ -56,7 +57,10 @@ def __init__(self, request, model, list_display, list_display_links,
5657
self.page_num = 0
5758
self.show_all = ALL_VAR in request.GET
5859
self.is_popup = IS_POPUP_VAR in request.GET
59-
self.to_field = request.GET.get(TO_FIELD_VAR)
60+
to_field = request.GET.get(TO_FIELD_VAR)
61+
if to_field and not model_admin.to_field_allowed(request, to_field):
62+
raise DisallowedModelAdminToField("The field %s cannot be referenced." % to_field)
63+
self.to_field = to_field
6064
self.params = dict(request.GET.items())
6165
if PAGE_VAR in self.params:
6266
del self.params[PAGE_VAR]

docs/releases/1.4.14.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,18 @@ and the ``RemoteUserBackend``, a change to the ``REMOTE_USER`` header between
4747
requests without an intervening logout could result in the prior user's session
4848
being co-opted by the subsequent user. The middleware now logs the user out on
4949
a failed login attempt.
50+
51+
Data leakage via query string manipulation in ``contrib.admin``
52+
===============================================================
53+
54+
In older versions of Django it was possible to reveal any field's data by
55+
modifying the "popup" and "to_field" parameters of the query string on an admin
56+
change form page. For example, requesting a URL like
57+
``/admin/auth/user/?pop=1&t=password`` and viewing the page's HTML allowed
58+
viewing the password hash of each user. While the admin requires users to have
59+
permissions to view the change form pages in the first place, this could leak
60+
data if you rely on users having access to view only certain fields on a model.
61+
62+
To address the issue, an exception will now be raised if a ``to_field`` value
63+
that isn't a related field to a model that has been registered with the admin
64+
is specified.

tests/regressiontests/admin_views/tests.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@
1313
from django.core.urlresolvers import reverse
1414
# Register auth models with the admin.
1515
from django.contrib import admin
16+
from django.contrib.admin.exceptions import DisallowedModelAdminToField
1617
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
1718
from django.contrib.admin.models import LogEntry, DELETION
1819
from django.contrib.admin.sites import LOGIN_FORM_KEY
1920
from django.contrib.admin.util import quote
20-
from django.contrib.admin.views.main import IS_POPUP_VAR
21+
from django.contrib.admin.views.main import IS_POPUP_VAR, TO_FIELD_VAR
2122
from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase
2223
from django.contrib.auth import REDIRECT_FIELD_NAME
2324
from django.contrib.auth.models import Group, User, Permission, UNUSABLE_PASSWORD
@@ -572,6 +573,19 @@ def test_disallowed_filtering(self):
572573
response = self.client.get("/test_admin/admin/admin_views/workhour/?employee__person_ptr__exact=%d" % e1.pk)
573574
self.assertEqual(response.status_code, 200)
574575

576+
def test_disallowed_to_field(self):
577+
with self.assertRaises(DisallowedModelAdminToField):
578+
response = self.client.get("/test_admin/admin/admin_views/section/", {TO_FIELD_VAR: 'missing_field'})
579+
580+
# Specifying a field that is not refered by any other model registered
581+
# to this admin site should raise an exception.
582+
with self.assertRaises(DisallowedModelAdminToField):
583+
response = self.client.get("/test_admin/admin/admin_views/section/", {TO_FIELD_VAR: 'name'})
584+
585+
# Specifying a field referenced by another model should be allowed.
586+
response = self.client.get("/test_admin/admin/admin_views/section/", {TO_FIELD_VAR: 'id'})
587+
self.assertEqual(response.status_code, 200)
588+
575589
def test_allowed_filtering_15103(self):
576590
"""
577591
Regressions test for ticket 15103 - filtering on fields defined in a
@@ -2061,10 +2075,9 @@ def test_with_fk_to_field(self):
20612075
"""Ensure that the to_field GET parameter is preserved when a search
20622076
is performed. Refs #10918.
20632077
"""
2064-
from django.contrib.admin.views.main import TO_FIELD_VAR
2065-
response = self.client.get('/test_admin/admin/auth/user/?q=joe&%s=username' % TO_FIELD_VAR)
2078+
response = self.client.get('/test_admin/admin/auth/user/?q=joe&%s=id' % TO_FIELD_VAR)
20662079
self.assertContains(response, "\n1 user\n")
2067-
self.assertContains(response, '<input type="hidden" name="t" value="username"/>', html=True)
2080+
self.assertContains(response, '<input type="hidden" name="%s" value="id"/>' % TO_FIELD_VAR, html=True)
20682081

20692082
def test_exact_matches(self):
20702083
response = self.client.get('/test_admin/admin/admin_views/recommendation/?q=bar')

0 commit comments

Comments
 (0)