Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

[1.1.X] Fix a security issue in the admin. Disclosure and new release…

… forthcoming.

git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.1.X@15035 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 17084839fd7e267da5729f2a27753322b9d415a0 1 parent 934dc9e
Alex Gaynor alex authored
28 django/contrib/admin/options.py
View
@@ -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."
8 django/contrib/admin/views/main.py
View
@@ -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
12 tests/regressiontests/admin_views/models.py
View
@@ -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)
5 tests/regressiontests/admin_views/tests.py
View
@@ -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']
Please sign in to comment.
Something went wrong with that request. Please try again.