CVE-2010-4534
Description
The administrative interface in django.contrib.admin in Django before 1.1.3, 1.2.x before 1.2.4, and 1.3.x before 1.3 beta 1 does not properly restrict use of the query string to perform certain object filtering, which allows remote authenticated users to obtain sensitive information via a series of requests containing regular expressions, as demonstrated by a created_by__password__regex parameter.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
DjangoPyPI | < 1.1.3 | 1.1.3 |
DjangoPyPI | >= 1.2, < 1.2.4 | 1.2.4 |
Affected products
16cpe:2.3:a:djangoproject:django:*:*:*:*:*:*:*:*+ 15 more
- cpe:2.3:a:djangoproject:django:*:*:*:*:*:*:*:*range: <=1.1.2
- cpe:2.3:a:djangoproject:django:0.91:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:0.95:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:0.95.1:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:0.96:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.0:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.0.1:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.0.2:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.1:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.1.0:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.2:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.2.1:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.2.2:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.2.3:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.3:alpha1:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.3:alpha2:*:*:*:*:*:*
Patches
217084839fd7e[1.1.X] Fix a security issue in the admin. Disclosure and new release forthcoming.
4 files changed · +49 −4
django/contrib/admin/options.py+27 −1 modified@@ -8,7 +8,9 @@ from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_ngettext, model_format_dict from django.core.exceptions import PermissionDenied, ValidationError from django.db import models, transaction -from django.db.models.fields import BLANK_CHOICE_DASH +from django.db.models.related import RelatedObject +from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist +from django.db.models.sql.constants import LOOKUP_SEP, QUERY_TERMS from django.http import Http404, HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404, render_to_response from django.utils.datastructures import SortedDict @@ -171,6 +173,30 @@ def _declared_fieldsets(self): return None declared_fieldsets = property(_declared_fieldsets) + def lookup_allowed(self, lookup): + parts = lookup.split(LOOKUP_SEP) + + # Last term in lookup is a query term (__exact, __startswith etc) + # This term can be ignored. + if len(parts) > 1 and parts[-1] in QUERY_TERMS: + parts.pop() + + # Special case -- foo__id__exact and foo__id queries are implied + # if foo has been specificially included in the lookup list; so + # drop __id if it is the last part. + if len(parts) > 1 and parts[-1] == self.model._meta.pk.name: + parts.pop() + + try: + self.model._meta.get_field_by_name(parts[0]) + except FieldDoesNotExist: + # Lookups on non-existants fields are ok, since they're ignored + # later. + return True + else: + clean_lookup = LOOKUP_SEP.join(parts) + return clean_lookup in self.list_filter or clean_lookup == self.date_hierarchy + class ModelAdmin(BaseModelAdmin): "Encapsulates all admin options and functionality for a given model."
django/contrib/admin/views/main.py+7 −1 modified@@ -1,6 +1,7 @@ from django.contrib.admin.filterspecs import FilterSpec from django.contrib.admin.options import IncorrectLookupParameters from django.contrib.admin.util import quote +from django.core.exceptions import SuspiciousOperation from django.core.paginator import Paginator, InvalidPage from django.db import models from django.db.models.query import QuerySet @@ -192,13 +193,18 @@ def get_query_set(self): else: lookup_params[key] = True + if not self.model_admin.lookup_allowed(key): + raise SuspiciousOperation( + "Filtering by %s not allowed" % key + ) + # Apply lookup parameters from the query string. try: qs = qs.filter(**lookup_params) # Naked except! Because we don't have any other way of validating "params". # They might be invalid if the keyword arguments are incorrect, or if the # values are not in the correct type, so we might get FieldError, ValueError, - # ValicationError, or ? from a custom field that raises yet something else + # ValicationError, or ? from a custom field that raises yet something else # when handed impossible data. except: raise IncorrectLookupParameters
tests/regressiontests/admin_views/models.py+10 −2 modified@@ -7,7 +7,7 @@ from django.core.mail import EmailMessage from django import forms from django.forms.models import BaseModelFormSet - +from django.contrib.auth.models import User from django.contrib.contenttypes import generic from django.contrib.contenttypes.models import ContentType @@ -89,7 +89,7 @@ class ChapterInline(admin.TabularInline): class ArticleAdmin(admin.ModelAdmin): list_display = ('content', 'date', callable_year, 'model_year', 'modeladmin_year') - list_filter = ('date',) + list_filter = ('date', 'section') def changelist_view(self, request): "Test that extra_context works" @@ -505,6 +505,13 @@ class CyclicTwo(models.Model): def __unicode__(self): return self.name +class Album(models.Model): + owner = models.ForeignKey(User) + title = models.CharField(max_length=30) + +class AlbumAdmin(admin.ModelAdmin): + list_filter = ['title'] + admin.site.register(Article, ArticleAdmin) admin.site.register(CustomArticle, CustomArticleAdmin) admin.site.register(Section, save_as=True, inlines=[ArticleInline]) @@ -547,3 +554,4 @@ def __unicode__(self): admin.site.register(Book, inlines=[ChapterInline]) admin.site.register(Promo) admin.site.register(ChapterXtra1) +admin.site.register(Album, AlbumAdmin) \ No newline at end of file
tests/regressiontests/admin_views/tests.py+5 −0 modified@@ -3,6 +3,7 @@ import re import datetime from django.conf import settings +from django.core.exceptions import SuspiciousOperation from django.core.files import temp as tempfile from django.contrib.auth.models import User, Permission from django.contrib.contenttypes.models import ContentType @@ -289,6 +290,10 @@ def testI18NLanguageNonEnglishFallback(self): self.assertContains(response, 'Choisir une heure') deactivate() + def test_disallowed_filtering(self): + self.assertRaises(SuspiciousOperation, + self.client.get, "/test_admin/admin/admin_views/album/?owner__email__startswith=fuzzy" + ) class SaveAsTests(TestCase): fixtures = ['admin-views-users.xml','admin-views-person.xml']
85207a245bf0Fix a security issue in the admin. Disclosure and new release forthcoming.
4 files changed · +44 −4
django/contrib/admin/options.py+27 −1 modified@@ -10,7 +10,9 @@ from django.views.decorators.csrf import csrf_protect from django.core.exceptions import PermissionDenied, ValidationError from django.db import models, transaction -from django.db.models.fields import BLANK_CHOICE_DASH +from django.db.models.related import RelatedObject +from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist +from django.db.models.sql.constants import LOOKUP_SEP, QUERY_TERMS from django.http import Http404, HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404, render_to_response from django.utils.decorators import method_decorator @@ -183,6 +185,30 @@ def _declared_fieldsets(self): def get_readonly_fields(self, request, obj=None): return self.readonly_fields + def lookup_allowed(self, lookup): + parts = lookup.split(LOOKUP_SEP) + + # Last term in lookup is a query term (__exact, __startswith etc) + # This term can be ignored. + if len(parts) > 1 and parts[-1] in QUERY_TERMS: + parts.pop() + + # Special case -- foo__id__exact and foo__id queries are implied + # if foo has been specificially included in the lookup list; so + # drop __id if it is the last part. + if len(parts) > 1 and parts[-1] == self.model._meta.pk.name: + parts.pop() + + try: + self.model._meta.get_field_by_name(parts[0]) + except FieldDoesNotExist: + # Lookups on non-existants fields are ok, since they're ignored + # later. + return True + else: + clean_lookup = LOOKUP_SEP.join(parts) + return clean_lookup in self.list_filter or clean_lookup == self.date_hierarchy + class ModelAdmin(BaseModelAdmin): "Encapsulates all admin options and functionality for a given model."
django/contrib/admin/views/main.py+7 −1 modified@@ -1,6 +1,7 @@ from django.contrib.admin.filterspecs import FilterSpec from django.contrib.admin.options import IncorrectLookupParameters from django.contrib.admin.util import quote +from django.core.exceptions import SuspiciousOperation from django.core.paginator import Paginator, InvalidPage from django.db import models from django.db.models.query import QuerySet @@ -187,13 +188,18 @@ def get_query_set(self): else: lookup_params[key] = True + if not self.model_admin.lookup_allowed(key): + raise SuspiciousOperation( + "Filtering by %s not allowed" % key + ) + # Apply lookup parameters from the query string. try: qs = qs.filter(**lookup_params) # Naked except! Because we don't have any other way of validating "params". # They might be invalid if the keyword arguments are incorrect, or if the # values are not in the correct type, so we might get FieldError, ValueError, - # ValicationError, or ? from a custom field that raises yet something else + # ValicationError, or ? from a custom field that raises yet something else # when handed impossible data. except: raise IncorrectLookupParameters
tests/regressiontests/admin_views/models.py+5 −2 modified@@ -92,7 +92,7 @@ class ChapterInline(admin.TabularInline): class ArticleAdmin(admin.ModelAdmin): list_display = ('content', 'date', callable_year, 'model_year', 'modeladmin_year') - list_filter = ('date',) + list_filter = ('date', 'section') def changelist_view(self, request): "Test that extra_context works" @@ -584,6 +584,9 @@ class Album(models.Model): owner = models.ForeignKey(User) title = models.CharField(max_length=30) +class AlbumAdmin(admin.ModelAdmin): + list_filter = ['title'] + admin.site.register(Article, ArticleAdmin) admin.site.register(CustomArticle, CustomArticleAdmin) admin.site.register(Section, save_as=True, inlines=[ArticleInline]) @@ -630,4 +633,4 @@ class Album(models.Model): admin.site.register(ChapterXtra1) admin.site.register(Pizza, PizzaAdmin) admin.site.register(Topping) -admin.site.register(Album) +admin.site.register(Album, AlbumAdmin)
tests/regressiontests/admin_views/tests.py+5 −0 modified@@ -4,6 +4,7 @@ import datetime from django.conf import settings +from django.core.exceptions import SuspiciousOperation from django.core.files import temp as tempfile # Register auth models with the admin. from django.contrib.auth import REDIRECT_FIELD_NAME, admin @@ -300,6 +301,10 @@ def testI18NLanguageNonEnglishFallback(self): self.assertContains(response, 'Choisir une heure') deactivate() + def test_disallowed_filtering(self): + self.assertRaises(SuspiciousOperation, + self.client.get, "/test_admin/admin/admin_views/album/?owner__email__startswith=fuzzy" + ) class SaveAsTests(TestCase): fixtures = ['admin-views-users.xml','admin-views-person.xml']
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
26- code.djangoproject.com/changeset/15031nvdPatchWEB
- www.djangoproject.com/weblog/2010/dec/22/security/nvdPatchVendor Advisory
- www.openwall.com/lists/oss-security/2010/12/23/4nvdPatchWEB
- www.openwall.com/lists/oss-security/2011/01/03/5nvdPatchWEB
- bugzilla.redhat.com/show_bug.cginvdPatchWEB
- archives.neohapsis.com/archives/fulldisclosure/2010-12/0580.htmlnvdExploitWEB
- evilpacket.net/2010/dec/22/information-leakage-django-administrative-interfac/nvdExploit
- ngenuity-is.com/advisories/2010/dec/22/information-leakage-in-django-administrative-inter/nvdExploit
- secunia.com/advisories/42715nvdVendor Advisory
- github.com/advisories/GHSA-fwr5-q9rx-294fghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2010-4534ghsaADVISORY
- evilpacket.net/2010/dec/22/information-leakage-django-administrative-interfacghsaWEB
- lists.fedoraproject.org/pipermail/package-announce/2011-January/053041.htmlnvdWEB
- lists.fedoraproject.org/pipermail/package-announce/2011-January/053072.htmlnvdWEB
- ngenuity-is.com/advisories/2010/dec/22/information-leakage-in-django-administrative-interghsaWEB
- www.djangoproject.com/weblog/2010/dec/22/securityghsaWEB
- www.ubuntu.com/usn/USN-1040-1nvdWEB
- github.com/django/django/commit/17084839fd7e267da5729f2a27753322b9d415a0ghsaWEB
- github.com/django/django/commit/85207a245bf09fdebe486b4c7bbcb65300f2a693ghsaWEB
- github.com/pypa/advisory-database/tree/main/vulns/django/PYSEC-2011-8.yamlghsaWEB
- secunia.com/advisories/42827nvd
- secunia.com/advisories/42913nvd
- www.securityfocus.com/archive/1/515446nvd
- www.securityfocus.com/bid/45562nvd
- www.vupen.com/english/advisories/2011/0048nvd
- www.vupen.com/english/advisories/2011/0098nvd
News mentions
0No linked articles in our index yet.