+61
-5
lines changedFilter options
+61
-5
lines changed Original file line number Diff line number Diff 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
Original file line number Diff line number Diff line change
@@ -269,6 +269,24 @@ def lookup_allowed(self, lookup, value):
269
269
clean_lookup = LOOKUP_SEP.join(parts)
270
270
return clean_lookup in self.list_filter or clean_lookup == self.date_hierarchy
271
271
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
+
272
290
def has_add_permission(self, request):
273
291
"""
274
292
Returns True if the given request has permission to add an object.
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@
10
10
from django.utils.http import urlencode
11
11
12
12
from django.contrib.admin import FieldListFilter
13
+
from django.contrib.admin.exceptions import DisallowedModelAdminToField
13
14
from django.contrib.admin.options import IncorrectLookupParameters
14
15
from django.contrib.admin.util import (quote, get_fields_from_path,
15
16
lookup_needs_distinct, prepare_lookup_value)
@@ -56,7 +57,10 @@ def __init__(self, request, model, list_display, list_display_links,
56
57
self.page_num = 0
57
58
self.show_all = ALL_VAR in request.GET
58
59
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
60
64
self.params = dict(request.GET.items())
61
65
if PAGE_VAR in self.params:
62
66
del self.params[PAGE_VAR]
Original file line number Diff line number Diff line change
@@ -47,3 +47,18 @@ and the ``RemoteUserBackend``, a change to the ``REMOTE_USER`` header between
47
47
requests without an intervening logout could result in the prior user's session
48
48
being co-opted by the subsequent user. The middleware now logs the user out on
49
49
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.
Original file line number Diff line number Diff line change
@@ -13,11 +13,12 @@
13
13
from django.core.urlresolvers import reverse
14
14
# Register auth models with the admin.
15
15
from django.contrib import admin
16
+
from django.contrib.admin.exceptions import DisallowedModelAdminToField
16
17
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
17
18
from django.contrib.admin.models import LogEntry, DELETION
18
19
from django.contrib.admin.sites import LOGIN_FORM_KEY
19
20
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
21
22
from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase
22
23
from django.contrib.auth import REDIRECT_FIELD_NAME
23
24
from django.contrib.auth.models import Group, User, Permission, UNUSABLE_PASSWORD
@@ -572,6 +573,19 @@ def test_disallowed_filtering(self):
572
573
response = self.client.get("/test_admin/admin/admin_views/workhour/?employee__person_ptr__exact=%d" % e1.pk)
573
574
self.assertEqual(response.status_code, 200)
574
575
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
+
575
589
def test_allowed_filtering_15103(self):
576
590
"""
577
591
Regressions test for ticket 15103 - filtering on fields defined in a
@@ -2061,10 +2075,9 @@ def test_with_fk_to_field(self):
2061
2075
"""Ensure that the to_field GET parameter is preserved when a search
2062
2076
is performed. Refs #10918.
2063
2077
"""
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)
2066
2079
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)
2068
2081
2069
2082
def test_exact_matches(self):
2070
2083
response = self.client.get('/test_admin/admin/admin_views/recommendation/?q=bar')
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