VYPR
Moderate severityNVD Advisory· Published Aug 26, 2014· Updated May 6, 2026

CVE-2014-0483

CVE-2014-0483

Description

The administrative interface (contrib.admin) in Django before 1.4.14, 1.5.x before 1.5.9, 1.6.x before 1.6.6, and 1.7 before release candidate 3 does not check if a field represents a relationship between models, which allows remote authenticated users to obtain sensitive information via a to_field parameter in a popup action to an admin change form page, as demonstrated by a /admin/auth/user/?pop=1&t=password URI.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
DjangoPyPI
< 1.4.141.4.14
DjangoPyPI
>= 1.5, < 1.5.91.5.9
DjangoPyPI
>= 1.6, < 1.6.61.6.6
DjangoPyPI
>= 1.7a1, < 1.7c31.7c3

Affected products

42
  • cpe:2.3:a:djangoproject:django:1.6.3:*:*:*:*:*:*:*+ 39 more
    • cpe:2.3:a:djangoproject:django:1.6.3:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.6.4:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.6.5:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:*:*:*:*:*:*:*:*range: <=1.4.13
    • cpe:2.3:a:djangoproject:django:1.4:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.4.1:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.4.2:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.4.4:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.4.5:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.4.6:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.4.7:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.4.8:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.4.9:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.4.10:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.4.11:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.4.12:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.7:beta1:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.7:beta2:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.7:beta3:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.7:beta4:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.7:rc1:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.5:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.5:alpha:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.5:beta:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.5.1:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.5.2:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.5.3:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.5.4:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.5.5:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.5.6:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.5.7:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.5.8:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.6:-:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.6:beta1:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.6:beta2:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.6:beta3:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.6:beta4:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.6.1:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.6.2:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.7:rc2:*:*:*:*:*:*
  • OpenSUSE/openSUSE2 versions
    cpe:2.3:o:opensuse:opensuse:12.3:*:*:*:*:*:*:*+ 1 more
    • cpe:2.3:o:opensuse:opensuse:12.3:*:*:*:*:*:*:*
    • cpe:2.3:o:opensuse:opensuse:13.1:*:*:*:*:*:*:*

Patches

4
2a446c896e7c

[1.5.x] Prevented data leakage in contrib.admin via query string manipulation.

https://github.com/django/djangoSimon CharetteAug 11, 2014via ghsa
6 files changed · +76 5
  • django/contrib/admin/exceptions.py+6 0 added
    @@ -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
    
  • django/contrib/admin/options.py+18 0 modified
    @@ -275,6 +275,24 @@ def lookup_allowed(self, lookup, value):
             clean_lookup = LOOKUP_SEP.join(parts)
             return clean_lookup in self.list_filter or clean_lookup == self.date_hierarchy
     
    +    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.
    
  • django/contrib/admin/views/main.py+5 1 modified
    @@ -12,6 +12,7 @@
     from django.utils.http import urlencode
     
     from django.contrib.admin import FieldListFilter
    +from django.contrib.admin.exceptions import DisallowedModelAdminToField
     from django.contrib.admin.options import IncorrectLookupParameters
     from django.contrib.admin.util import (quote, get_fields_from_path,
         lookup_needs_distinct, prepare_lookup_value)
    @@ -58,7 +59,10 @@ def __init__(self, request, model, list_display, list_display_links,
                 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]
    
  • docs/releases/1.4.14.txt+15 0 modified
    @@ -47,3 +47,18 @@ and the ``RemoteUserBackend``, a change to the ``REMOTE_USER`` header between
     requests without an intervening logout could result in the prior user's session
     being co-opted by the subsequent user. The middleware now logs the user out on
     a failed login attempt.
    +
    +Data leakage via query string manipulation in ``contrib.admin``
    +===============================================================
    +
    +In older versions of Django it was possible to reveal any field's data by
    +modifying the "popup" and "to_field" parameters of the query string on an admin
    +change form page. For example, requesting a URL like
    +``/admin/auth/user/?pop=1&t=password`` and viewing the page's HTML allowed
    +viewing the password hash of each user. While the admin requires users to have
    +permissions to view the change form pages in the first place, this could leak
    +data if you rely on users having access to view only certain fields on a model.
    +
    +To address the issue, an exception will now be raised if a ``to_field`` value
    +that isn't a related field to a model that has been registered with the admin
    +is specified.
    
  • docs/releases/1.5.9.txt+15 0 modified
    @@ -47,3 +47,18 @@ and the ``RemoteUserBackend``, a change to the ``REMOTE_USER`` header between
     requests without an intervening logout could result in the prior user's session
     being co-opted by the subsequent user. The middleware now logs the user out on
     a failed login attempt.
    +
    +Data leakage via query string manipulation in ``contrib.admin``
    +===============================================================
    +
    +In older versions of Django it was possible to reveal any field's data by
    +modifying the "popup" and "to_field" parameters of the query string on an admin
    +change form page. For example, requesting a URL like
    +``/admin/auth/user/?pop=1&t=password`` and viewing the page's HTML allowed
    +viewing the password hash of each user. While the admin requires users to have
    +permissions to view the change form pages in the first place, this could leak
    +data if you rely on users having access to view only certain fields on a model.
    +
    +To address the issue, an exception will now be raised if a ``to_field`` value
    +that isn't a related field to a model that has been registered with the admin
    +is specified.
    
  • tests/regressiontests/admin_views/tests.py+17 4 modified
    @@ -16,11 +16,12 @@
     from django.core.urlresolvers import reverse
     # Register auth models with the admin.
     from django.contrib import admin
    +from django.contrib.admin.exceptions import DisallowedModelAdminToField
     from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
     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 IS_POPUP_VAR
    +from django.contrib.admin.views.main import IS_POPUP_VAR, TO_FIELD_VAR
     from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase
     from django.contrib.auth import REDIRECT_FIELD_NAME
     from django.contrib.auth.models import Group, User, Permission, UNUSABLE_PASSWORD
    @@ -557,6 +558,19 @@ def test_disallowed_filtering(self):
             response = self.client.get("/test_admin/admin/admin_views/workhour/?employee__person_ptr__exact=%d" % e1.pk)
             self.assertEqual(response.status_code, 200)
     
    +    def test_disallowed_to_field(self):
    +        with self.assertRaises(DisallowedModelAdminToField):
    +            response = 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.
    +        with self.assertRaises(DisallowedModelAdminToField):
    +            response = 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)
    +
         def test_allowed_filtering_15103(self):
             """
             Regressions test for ticket 15103 - filtering on fields defined in a
    @@ -2138,10 +2152,9 @@ def test_with_fk_to_field(self):
             """Ensure that the to_field GET parameter is preserved when a search
             is performed. Refs #10918.
             """
    -        from django.contrib.admin.views.main import TO_FIELD_VAR
    -        response = self.client.get('/test_admin/admin/auth/user/?q=joe&%s=username' % TO_FIELD_VAR)
    +        response = self.client.get('/test_admin/admin/auth/user/?q=joe&%s=id' % TO_FIELD_VAR)
             self.assertContains(response, "\n1 user\n")
    -        self.assertContains(response, '<input type="hidden" name="t" value="username"/>', html=True)
    +        self.assertContains(response, '<input type="hidden" name="%s" value="id"/>' % TO_FIELD_VAR, html=True)
     
         def test_exact_matches(self):
             response = self.client.get('/test_admin/admin/admin_views/recommendation/?q=bar')
    
027bd3486420

[1.4.x] Prevented data leakage in contrib.admin via query string manipulation.

https://github.com/django/djangoSimon CharetteAug 11, 2014via ghsa
5 files changed · +61 5
  • django/contrib/admin/exceptions.py+6 0 added
    @@ -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
    
  • django/contrib/admin/options.py+18 0 modified
    @@ -269,6 +269,24 @@ def lookup_allowed(self, lookup, value):
             clean_lookup = LOOKUP_SEP.join(parts)
             return clean_lookup in self.list_filter or clean_lookup == self.date_hierarchy
     
    +    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.
    
  • django/contrib/admin/views/main.py+5 1 modified
    @@ -10,6 +10,7 @@
     from django.utils.http import urlencode
     
     from django.contrib.admin import FieldListFilter
    +from django.contrib.admin.exceptions import DisallowedModelAdminToField
     from django.contrib.admin.options import IncorrectLookupParameters
     from django.contrib.admin.util import (quote, get_fields_from_path,
         lookup_needs_distinct, prepare_lookup_value)
    @@ -56,7 +57,10 @@ def __init__(self, request, model, list_display, list_display_links,
                 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]
    
  • docs/releases/1.4.14.txt+15 0 modified
    @@ -47,3 +47,18 @@ and the ``RemoteUserBackend``, a change to the ``REMOTE_USER`` header between
     requests without an intervening logout could result in the prior user's session
     being co-opted by the subsequent user. The middleware now logs the user out on
     a failed login attempt.
    +
    +Data leakage via query string manipulation in ``contrib.admin``
    +===============================================================
    +
    +In older versions of Django it was possible to reveal any field's data by
    +modifying the "popup" and "to_field" parameters of the query string on an admin
    +change form page. For example, requesting a URL like
    +``/admin/auth/user/?pop=1&t=password`` and viewing the page's HTML allowed
    +viewing the password hash of each user. While the admin requires users to have
    +permissions to view the change form pages in the first place, this could leak
    +data if you rely on users having access to view only certain fields on a model.
    +
    +To address the issue, an exception will now be raised if a ``to_field`` value
    +that isn't a related field to a model that has been registered with the admin
    +is specified.
    
  • tests/regressiontests/admin_views/tests.py+17 4 modified
    @@ -13,11 +13,12 @@
     from django.core.urlresolvers import reverse
     # Register auth models with the admin.
     from django.contrib import admin
    +from django.contrib.admin.exceptions import DisallowedModelAdminToField
     from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
     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 IS_POPUP_VAR
    +from django.contrib.admin.views.main import IS_POPUP_VAR, TO_FIELD_VAR
     from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase
     from django.contrib.auth import REDIRECT_FIELD_NAME
     from django.contrib.auth.models import Group, User, Permission, UNUSABLE_PASSWORD
    @@ -572,6 +573,19 @@ def test_disallowed_filtering(self):
             response = self.client.get("/test_admin/admin/admin_views/workhour/?employee__person_ptr__exact=%d" % e1.pk)
             self.assertEqual(response.status_code, 200)
     
    +    def test_disallowed_to_field(self):
    +        with self.assertRaises(DisallowedModelAdminToField):
    +            response = 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.
    +        with self.assertRaises(DisallowedModelAdminToField):
    +            response = 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)
    +
         def test_allowed_filtering_15103(self):
             """
             Regressions test for ticket 15103 - filtering on fields defined in a
    @@ -2061,10 +2075,9 @@ def test_with_fk_to_field(self):
             """Ensure that the to_field GET parameter is preserved when a search
             is performed. Refs #10918.
             """
    -        from django.contrib.admin.views.main import TO_FIELD_VAR
    -        response = self.client.get('/test_admin/admin/auth/user/?q=joe&%s=username' % TO_FIELD_VAR)
    +        response = self.client.get('/test_admin/admin/auth/user/?q=joe&%s=id' % TO_FIELD_VAR)
             self.assertContains(response, "\n1 user\n")
    -        self.assertContains(response, '<input type="hidden" name="t" value="username"/>', html=True)
    +        self.assertContains(response, '<input type="hidden" name="%s" value="id"/>' % TO_FIELD_VAR, html=True)
     
         def test_exact_matches(self):
             response = self.client.get('/test_admin/admin/admin_views/recommendation/?q=bar')
    
f7c494f25062

[1.6.x] Prevented data leakage in contrib.admin via query string manipulation.

https://github.com/django/djangoSimon CharetteAug 7, 2014via ghsa
8 files changed · +94 5
  • django/contrib/admin/exceptions.py+5 0 modified
    @@ -4,3 +4,8 @@
     class DisallowedModelAdminLookup(SuspiciousOperation):
         """Invalid filter was passed to admin view via URL querystring"""
         pass
    +
    +
    +class DisallowedModelAdminToField(SuspiciousOperation):
    +    """Invalid to_field was passed to admin view via URL query string"""
    +    pass
    
  • django/contrib/admin/options.py+18 0 modified
    @@ -327,6 +327,24 @@ def lookup_allowed(self, lookup, value):
             clean_lookup = LOOKUP_SEP.join(parts)
             return clean_lookup in self.list_filter or clean_lookup == self.date_hierarchy
     
    +    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 in related_object.field.foreign_related_fields):
    +                return True
    +
    +        return False
    +
         def has_add_permission(self, request):
             """
             Returns True if the given request has permission to add an object.
    
  • django/contrib/admin/views/main.py+5 2 modified
    @@ -14,7 +14,7 @@
     from django.utils.http import urlencode
     
     from django.contrib.admin import FieldListFilter
    -from django.contrib.admin.exceptions import DisallowedModelAdminLookup
    +from django.contrib.admin.exceptions import DisallowedModelAdminLookup, DisallowedModelAdminToField
     from django.contrib.admin.options import IncorrectLookupParameters, IS_POPUP_VAR
     from django.contrib.admin.util import (quote, get_fields_from_path,
         lookup_needs_distinct, prepare_lookup_value)
    @@ -90,7 +90,10 @@ def __init__(self, request, model, list_display, list_display_links,
                 self.page_num = 0
             self.show_all = ALL_VAR in request.GET
             self.is_popup = _is_changelist_popup(request)
    -        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]
    
  • docs/ref/exceptions.txt+1 0 modified
    @@ -56,6 +56,7 @@ SuspiciousOperation
     
         * DisallowedHost
         * DisallowedModelAdminLookup
    +    * DisallowedModelAdminToField
         * DisallowedRedirect
         * InvalidSessionKey
         * SuspiciousFileOperation
    
  • docs/releases/1.4.14.txt+15 0 modified
    @@ -47,3 +47,18 @@ and the ``RemoteUserBackend``, a change to the ``REMOTE_USER`` header between
     requests without an intervening logout could result in the prior user's session
     being co-opted by the subsequent user. The middleware now logs the user out on
     a failed login attempt.
    +
    +Data leakage via query string manipulation in ``contrib.admin``
    +===============================================================
    +
    +In older versions of Django it was possible to reveal any field's data by
    +modifying the "popup" and "to_field" parameters of the query string on an admin
    +change form page. For example, requesting a URL like
    +``/admin/auth/user/?pop=1&t=password`` and viewing the page's HTML allowed
    +viewing the password hash of each user. While the admin requires users to have
    +permissions to view the change form pages in the first place, this could leak
    +data if you rely on users having access to view only certain fields on a model.
    +
    +To address the issue, an exception will now be raised if a ``to_field`` value
    +that isn't a related field to a model that has been registered with the admin
    +is specified.
    
  • docs/releases/1.5.9.txt+15 0 modified
    @@ -47,3 +47,18 @@ and the ``RemoteUserBackend``, a change to the ``REMOTE_USER`` header between
     requests without an intervening logout could result in the prior user's session
     being co-opted by the subsequent user. The middleware now logs the user out on
     a failed login attempt.
    +
    +Data leakage via query string manipulation in ``contrib.admin``
    +===============================================================
    +
    +In older versions of Django it was possible to reveal any field's data by
    +modifying the "popup" and "to_field" parameters of the query string on an admin
    +change form page. For example, requesting a URL like
    +``/admin/auth/user/?pop=1&t=password`` and viewing the page's HTML allowed
    +viewing the password hash of each user. While the admin requires users to have
    +permissions to view the change form pages in the first place, this could leak
    +data if you rely on users having access to view only certain fields on a model.
    +
    +To address the issue, an exception will now be raised if a ``to_field`` value
    +that isn't a related field to a model that has been registered with the admin
    +is specified.
    
  • docs/releases/1.6.6.txt+15 0 modified
    @@ -48,6 +48,21 @@ requests without an intervening logout could result in the prior user's session
     being co-opted by the subsequent user. The middleware now logs the user out on
     a failed login attempt.
     
    +Data leakage via query string manipulation in ``contrib.admin``
    +===============================================================
    +
    +In older versions of Django it was possible to reveal any field's data by
    +modifying the "popup" and "to_field" parameters of the query string on an admin
    +change form page. For example, requesting a URL like
    +``/admin/auth/user/?_popup=1&t=password`` and viewing the page's HTML allowed
    +viewing the password hash of each user. While the admin requires users to have
    +permissions to view the change form pages in the first place, this could leak
    +data if you rely on users having access to view only certain fields on a model.
    +
    +To address the issue, an exception will now be raised if a ``to_field`` value
    +that isn't a related field to a model that has been registered with the admin
    +is specified.
    +
     Bugfixes
     ========
     
    
  • tests/admin_views/tests.py+20 3 modified
    @@ -14,6 +14,7 @@
     from django.contrib import admin
     from django.contrib.auth import get_permission_codename
     from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
    +from django.contrib.admin.views.main import TO_FIELD_VAR
     from django.contrib.admin.models import LogEntry, DELETION
     from django.contrib.admin.sites import LOGIN_FORM_KEY
     from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
    @@ -577,6 +578,23 @@ def test_disallowed_filtering(self):
             response = self.client.get("/test_admin/admin/admin_views/workhour/?employee__person_ptr__exact=%d" % e1.pk)
             self.assertEqual(response.status_code, 200)
     
    +    def test_disallowed_to_field(self):
    +        with patch_logger('django.security.DisallowedModelAdminToField', 'error') as calls:
    +            response = self.client.get("/test_admin/admin/admin_views/section/", {TO_FIELD_VAR: 'missing_field'})
    +            self.assertEqual(response.status_code, 400)
    +            self.assertEqual(len(calls), 1)
    +
    +        # Specifying a field that is not refered by any other model registered
    +        # to this admin site should raise an exception.
    +        with patch_logger('django.security.DisallowedModelAdminToField', 'error') as calls:
    +            response = self.client.get("/test_admin/admin/admin_views/section/", {TO_FIELD_VAR: 'name'})
    +            self.assertEqual(response.status_code, 400)
    +            self.assertEqual(len(calls), 1)
    +
    +        # 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)
    +
         def test_allowed_filtering_15103(self):
             """
             Regressions test for ticket 15103 - filtering on fields defined in a
    @@ -2204,10 +2222,9 @@ def test_with_fk_to_field(self):
             """Ensure that the to_field GET parameter is preserved when a search
             is performed. Refs #10918.
             """
    -        from django.contrib.admin.views.main import TO_FIELD_VAR
    -        response = self.client.get('/test_admin/admin/auth/user/?q=joe&%s=username' % TO_FIELD_VAR)
    +        response = self.client.get('/test_admin/admin/auth/user/?q=joe&%s=id' % TO_FIELD_VAR)
             self.assertContains(response, "\n1 user\n")
    -        self.assertContains(response, '<input type="hidden" name="t" value="username"/>', html=True)
    +        self.assertContains(response, '<input type="hidden" name="%s" value="id"/>' % TO_FIELD_VAR, html=True)
     
         def test_exact_matches(self):
             response = self.client.get('/test_admin/admin/admin_views/recommendation/?q=bar')
    
2b31342cdf14

[1.7.x] Prevented data leakage in contrib.admin via query string manipulation.

https://github.com/django/djangoSimon CharetteAug 7, 2014via ghsa
8 files changed · +115 7
  • django/contrib/admin/exceptions.py+5 0 modified
    @@ -4,3 +4,8 @@
     class DisallowedModelAdminLookup(SuspiciousOperation):
         """Invalid filter was passed to admin view via URL querystring"""
         pass
    +
    +
    +class DisallowedModelAdminToField(SuspiciousOperation):
    +    """Invalid to_field was passed to admin view via URL query string"""
    +    pass
    
  • django/contrib/admin/options.py+24 2 modified
    @@ -11,6 +11,7 @@
     from django.contrib.admin import validation
     from django.contrib.admin.checks import (BaseModelAdminChecks, ModelAdminChecks,
         InlineModelAdminChecks)
    +from django.contrib.admin.exceptions import DisallowedModelAdminToField
     from django.contrib.admin.utils import (quote, unquote, flatten_fieldsets,
         get_deleted_objects, model_format_dict, NestedObjects,
         lookup_needs_distinct)
    @@ -434,6 +435,24 @@ def lookup_allowed(self, lookup, value):
                     valid_lookups.append(filter_item)
             return clean_lookup in valid_lookups
     
    +    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 in related_object.field.foreign_related_fields):
    +                return True
    +
    +        return False
    +
         def has_add_permission(self, request):
             """
             Returns True if the given request has permission to add an object.
    @@ -1325,6 +1344,10 @@ def get_changeform_initial_data(self, request):
         @transaction.atomic
         def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
     
    +        to_field = request.POST.get(TO_FIELD_VAR, request.GET.get(TO_FIELD_VAR))
    +        if to_field and not self.to_field_allowed(request, to_field):
    +            raise DisallowedModelAdminToField("The field %s cannot be referenced." % to_field)
    +
             model = self.model
             opts = model._meta
             add = object_id is None
    @@ -1397,8 +1420,7 @@ def changeform_view(self, request, object_id=None, form_url='', extra_context=No
                 original=obj,
                 is_popup=(IS_POPUP_VAR in request.POST or
                           IS_POPUP_VAR in request.GET),
    -            to_field=request.POST.get(TO_FIELD_VAR,
    -                                      request.GET.get(TO_FIELD_VAR)),
    +            to_field=to_field,
                 media=media,
                 inline_admin_formsets=inline_formsets,
                 errors=helpers.AdminErrorList(form, formsets),
    
  • django/contrib/admin/views/main.py+7 2 modified
    @@ -14,7 +14,9 @@
     from django.utils.http import urlencode
     
     from django.contrib.admin import FieldListFilter
    -from django.contrib.admin.exceptions import DisallowedModelAdminLookup
    +from django.contrib.admin.exceptions import (
    +    DisallowedModelAdminLookup, DisallowedModelAdminToField,
    +)
     from django.contrib.admin.options import IncorrectLookupParameters, IS_POPUP_VAR, TO_FIELD_VAR
     from django.contrib.admin.utils import (quote, get_fields_from_path,
         lookup_needs_distinct, prepare_lookup_value)
    @@ -89,7 +91,10 @@ def __init__(self, request, model, list_display, list_display_links,
                 self.page_num = 0
             self.show_all = ALL_VAR in request.GET
             self.is_popup = _is_changelist_popup(request)
    -        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]
    
  • docs/ref/exceptions.txt+1 0 modified
    @@ -56,6 +56,7 @@ SuspiciousOperation
     
         * DisallowedHost
         * DisallowedModelAdminLookup
    +    * DisallowedModelAdminToField
         * DisallowedRedirect
         * InvalidSessionKey
         * SuspiciousFileOperation
    
  • docs/releases/1.4.14.txt+15 0 modified
    @@ -47,3 +47,18 @@ and the ``RemoteUserBackend``, a change to the ``REMOTE_USER`` header between
     requests without an intervening logout could result in the prior user's session
     being co-opted by the subsequent user. The middleware now logs the user out on
     a failed login attempt.
    +
    +Data leakage via query string manipulation in ``contrib.admin``
    +===============================================================
    +
    +In older versions of Django it was possible to reveal any field's data by
    +modifying the "popup" and "to_field" parameters of the query string on an admin
    +change form page. For example, requesting a URL like
    +``/admin/auth/user/?pop=1&t=password`` and viewing the page's HTML allowed
    +viewing the password hash of each user. While the admin requires users to have
    +permissions to view the change form pages in the first place, this could leak
    +data if you rely on users having access to view only certain fields on a model.
    +
    +To address the issue, an exception will now be raised if a ``to_field`` value
    +that isn't a related field to a model that has been registered with the admin
    +is specified.
    
  • docs/releases/1.5.9.txt+15 0 modified
    @@ -47,3 +47,18 @@ and the ``RemoteUserBackend``, a change to the ``REMOTE_USER`` header between
     requests without an intervening logout could result in the prior user's session
     being co-opted by the subsequent user. The middleware now logs the user out on
     a failed login attempt.
    +
    +Data leakage via query string manipulation in ``contrib.admin``
    +===============================================================
    +
    +In older versions of Django it was possible to reveal any field's data by
    +modifying the "popup" and "to_field" parameters of the query string on an admin
    +change form page. For example, requesting a URL like
    +``/admin/auth/user/?pop=1&t=password`` and viewing the page's HTML allowed
    +viewing the password hash of each user. While the admin requires users to have
    +permissions to view the change form pages in the first place, this could leak
    +data if you rely on users having access to view only certain fields on a model.
    +
    +To address the issue, an exception will now be raised if a ``to_field`` value
    +that isn't a related field to a model that has been registered with the admin
    +is specified.
    
  • docs/releases/1.6.6.txt+15 0 modified
    @@ -48,6 +48,21 @@ requests without an intervening logout could result in the prior user's session
     being co-opted by the subsequent user. The middleware now logs the user out on
     a failed login attempt.
     
    +Data leakage via query string manipulation in ``contrib.admin``
    +===============================================================
    +
    +In older versions of Django it was possible to reveal any field's data by
    +modifying the "popup" and "to_field" parameters of the query string on an admin
    +change form page. For example, requesting a URL like
    +``/admin/auth/user/?_popup=1&t=password`` and viewing the page's HTML allowed
    +viewing the password hash of each user. While the admin requires users to have
    +permissions to view the change form pages in the first place, this could leak
    +data if you rely on users having access to view only certain fields on a model.
    +
    +To address the issue, an exception will now be raised if a ``to_field`` value
    +that isn't a related field to a model that has been registered with the admin
    +is specified.
    +
     Bugfixes
     ========
     
    
  • tests/admin_views/tests.py+33 3 modified
    @@ -18,6 +18,7 @@
     from django.contrib.admin import ModelAdmin
     from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
     from django.contrib.admin.models import LogEntry, DELETION
    +from django.contrib.admin.options import TO_FIELD_VAR
     from django.contrib.admin.templatetags.admin_static import static
     from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
     from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase
    @@ -599,6 +600,36 @@ def test_disallowed_filtering(self):
             response = self.client.get("/test_admin/admin/admin_views/workhour/?employee__person_ptr__exact=%d" % e1.pk)
             self.assertEqual(response.status_code, 200)
     
    +    def test_disallowed_to_field(self):
    +        with patch_logger('django.security.DisallowedModelAdminToField', 'error') as calls:
    +            response = self.client.get("/test_admin/admin/admin_views/section/", {TO_FIELD_VAR: 'missing_field'})
    +            self.assertEqual(response.status_code, 400)
    +            self.assertEqual(len(calls), 1)
    +
    +        # Specifying a field that is not refered by any other model registered
    +        # to this admin site should raise an exception.
    +        with patch_logger('django.security.DisallowedModelAdminToField', 'error') as calls:
    +            response = self.client.get("/test_admin/admin/admin_views/section/", {TO_FIELD_VAR: 'name'})
    +            self.assertEqual(response.status_code, 400)
    +            self.assertEqual(len(calls), 1)
    +
    +        # 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)
    +
    +        # We also want to prevent the add and change view from leaking a
    +        # disallowed field value.
    +        with patch_logger('django.security.DisallowedModelAdminToField', 'error') as calls:
    +            response = self.client.post("/test_admin/admin/admin_views/section/add/", {TO_FIELD_VAR: 'name'})
    +            self.assertEqual(response.status_code, 400)
    +            self.assertEqual(len(calls), 1)
    +
    +        section = Section.objects.create()
    +        with patch_logger('django.security.DisallowedModelAdminToField', 'error') as calls:
    +            response = self.client.post("/test_admin/admin/admin_views/section/%d/" % section.pk, {TO_FIELD_VAR: 'name'})
    +            self.assertEqual(response.status_code, 400)
    +            self.assertEqual(len(calls), 1)
    +
         def test_allowed_filtering_15103(self):
             """
             Regressions test for ticket 15103 - filtering on fields defined in a
    @@ -2310,10 +2341,9 @@ def test_with_fk_to_field(self):
             """Ensure that the to_field GET parameter is preserved when a search
             is performed. Refs #10918.
             """
    -        from django.contrib.admin.views.main import TO_FIELD_VAR
    -        response = self.client.get('/test_admin/admin/auth/user/?q=joe&%s=username' % TO_FIELD_VAR)
    +        response = self.client.get('/test_admin/admin/auth/user/?q=joe&%s=id' % TO_FIELD_VAR)
             self.assertContains(response, "\n1 user\n")
    -        self.assertContains(response, '<input type="hidden" name="_to_field" value="username"/>', html=True)
    +        self.assertContains(response, '<input type="hidden" name="%s" value="id"/>' % TO_FIELD_VAR, html=True)
     
         def test_exact_matches(self):
             response = self.client.get('/test_admin/admin/admin_views/recommendation/?q=bar')
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

17

News mentions

0

No linked articles in our index yet.