Description: Prevented data leakage in contrib.admin via query string manipulation
 Patch backported by Thorsten Alteholz and Raphaël Hertzog.
Origin: backport, https://github.com/django/django/commit/027bd348642007617518379f8b02546abacaa6e0
Author: Simon Charette <charette.s@gmail.com>
Last-Update: 2014-09-26

--- /dev/null
+++ b/django/contrib/admin/exceptions.py
@@ -0,0 +1,6 @@
+from django.core.exceptions import SuspiciousOperation
+
+
+class DisallowedModelAdminToField(SuspiciousOperation):
+    """Invalid to_field was passed to admin view via URL query string"""
+    pass
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -306,6 +306,24 @@ class ModelAdmin(BaseModelAdmin):
         return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
     media = property(_media)
 
+    def to_field_allowed(self, request, to_field):
+        opts = self.model._meta
+
+        try:
+            field = opts.get_field(to_field)
+        except FieldDoesNotExist:
+            return False
+
+        # Make sure at least one of the models registered for this site
+        # references this field.
+        registered_models = self.admin_site._registry
+        for related_object in opts.get_all_related_objects():
+            if (related_object.model in registered_models and
+                    field == related_object.field.rel.get_related_field()):
+                return True
+
+        return False
+
     def has_add_permission(self, request):
         "Returns True if the given request has permission to add an object."
         opts = self.opts
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -1,4 +1,5 @@
 from django.contrib.admin.filterspecs import FilterSpec
+from django.contrib.admin.exceptions import DisallowedModelAdminToField
 from django.contrib.admin.options import IncorrectLookupParameters
 from django.contrib.admin.util import quote
 from django.core.exceptions import SuspiciousOperation
@@ -50,7 +51,10 @@ class ChangeList(object):
             self.page_num = 0
         self.show_all = ALL_VAR in request.GET
         self.is_popup = IS_POPUP_VAR in request.GET
-        self.to_field = request.GET.get(TO_FIELD_VAR)
+        to_field = request.GET.get(TO_FIELD_VAR)
+        if to_field and not model_admin.to_field_allowed(request, to_field):
+            raise DisallowedModelAdminToField("The field %s cannot be referenced." % to_field)
+        self.to_field = to_field
         self.params = dict(request.GET.items())
         if PAGE_VAR in self.params:
             del self.params[PAGE_VAR]
--- a/tests/regressiontests/admin_views/tests.py
+++ b/tests/regressiontests/admin_views/tests.py
@@ -11,6 +11,8 @@ from django.contrib.contenttypes.models
 from django.contrib.admin.models import LogEntry, DELETION
 from django.contrib.admin.sites import LOGIN_FORM_KEY
 from django.contrib.admin.util import quote
+from django.contrib.admin.views.main import TO_FIELD_VAR
+from django.contrib.admin.exceptions import DisallowedModelAdminToField
 from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
 from django.forms.util import ErrorList
 from django.test import TestCase
@@ -303,6 +305,22 @@ class AdminViewBasicTest(TestCase):
             self.client.get, "/test_admin/admin/admin_views/album/?owner__email__startswith=fuzzy"
         )
 
+    def test_disallowed_to_field(self):
+        self.assertRaises(DisallowedModelAdminToField,
+            self.client.get, "/test_admin/admin/admin_views/section/",
+            {TO_FIELD_VAR: 'missing_field'})
+
+        # Specifying a field that is not refered by any other model registered
+        # to this admin site should raise an exception.
+        self.assertRaises(DisallowedModelAdminToField,
+            self.client.get, "/test_admin/admin/admin_views/section/",
+            {TO_FIELD_VAR: 'name'})
+
+        # Specifying a field referenced by another model should be allowed.
+        response = self.client.get("/test_admin/admin/admin_views/section/",
+                                   {TO_FIELD_VAR: 'id'})
+        self.assertEqual(response.status_code, 200)
+
 class SaveAsTests(TestCase):
     fixtures = ['admin-views-users.xml','admin-views-person.xml']
 
