Moderate severityNVD Advisory· Published May 2, 2013· Updated Apr 29, 2026
CVE-2013-0305
CVE-2013-0305
Description
The administrative interface for Django 1.3.x before 1.3.6, 1.4.x before 1.4.4, and 1.5 before release candidate 2 does not check permissions for the history view, which allows remote authenticated administrators to obtain sensitive object history information.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
DjangoPyPI | >= 1.3, < 1.3.6 | 1.3.6 |
DjangoPyPI | >= 1.4, < 1.4.4 | 1.4.4 |
Affected products
17cpe:2.3:a:djangoproject:django:1.3:*:*:*:*:*:*:*+ 12 more
- cpe:2.3:a:djangoproject:django:1.3:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.3.1:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.3.2:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.3.3:*:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.3:alpha1:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.3:beta1:*:*:*:*:*:*
- 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:alpha:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.4:beta:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.5:alpha:*:*:*:*:*:*
- cpe:2.3:a:djangoproject:django:1.5:beta:*:*:*:*:*:*
cpe:2.3:o:canonical:ubuntu_linux:10.04:-:lts:*:*:*:*:*+ 3 more
- cpe:2.3:o:canonical:ubuntu_linux:10.04:-:lts:*:*:*:*:*
- cpe:2.3:o:canonical:ubuntu_linux:11.10:*:*:*:*:*:*:*
- cpe:2.3:o:canonical:ubuntu_linux:12.04:-:lts:*:*:*:*:*
- cpe:2.3:o:canonical:ubuntu_linux:12.10:*:*:*:*:*:*:*
Patches
20e7861aec737[1.4.x] Checked object permissions on admin history view.
2 files changed · +48 −2
django/contrib/admin/options.py+8 −2 modified@@ -1317,15 +1317,21 @@ def delete_view(self, request, object_id, extra_context=None): def history_view(self, request, object_id, extra_context=None): "The 'history' admin view for this model." from django.contrib.admin.models import LogEntry + # First check if the user can see this history. model = self.model + obj = get_object_or_404(model, pk=unquote(object_id)) + + if not self.has_change_permission(request, obj): + raise PermissionDenied + + # Then get the history for this object. opts = model._meta app_label = opts.app_label action_list = LogEntry.objects.filter( object_id = object_id, content_type__id__exact = ContentType.objects.get_for_model(model).id ).select_related().order_by('action_time') - # If no history was found, see whether this object even exists. - obj = get_object_or_404(model, pk=unquote(object_id)) + context = { 'title': _('Change history: %s') % force_unicode(obj), 'action_list': action_list,
tests/regressiontests/admin_views/tests.py+40 −0 modified@@ -1064,6 +1064,46 @@ def testChangeView(self): self.assertContains(request, 'login-form') self.client.get('/test_admin/admin/logout/') + def testHistoryView(self): + """History view should restrict access.""" + + # add user shoud not be able to view the list of article or change any of them + self.client.get('/test_admin/admin/') + self.client.post('/test_admin/admin/', self.adduser_login) + response = self.client.get('/test_admin/admin/admin_views/article/1/history/') + self.assertEqual(response.status_code, 403) + self.client.get('/test_admin/admin/logout/') + + # change user can view all items and edit them + self.client.get('/test_admin/admin/') + self.client.post('/test_admin/admin/', self.changeuser_login) + response = self.client.get('/test_admin/admin/admin_views/article/1/history/') + self.assertEqual(response.status_code, 200) + + # Test redirection when using row-level change permissions. Refs #11513. + RowLevelChangePermissionModel.objects.create(id=1, name="odd id") + RowLevelChangePermissionModel.objects.create(id=2, name="even id") + for login_dict in [self.super_login, self.changeuser_login, self.adduser_login, self.deleteuser_login]: + self.client.post('/test_admin/admin/', login_dict) + response = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/1/history/') + self.assertEqual(response.status_code, 403) + + response = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/2/history/') + self.assertEqual(response.status_code, 200) + + self.client.get('/test_admin/admin/logout/') + + for login_dict in [self.joepublic_login, self.no_username_login]: + self.client.post('/test_admin/admin/', login_dict) + response = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/1/history/') + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'login-form') + response = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/2/history/') + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'login-form') + + self.client.get('/test_admin/admin/logout/') + def testConditionallyShowAddSectionLink(self): """ The foreign key widget should only show the "add related" button if the
d3a45e10c8ac[1.3.x] Checked object permissions on admin history view.
2 files changed · +48 −2
django/contrib/admin/options.py+8 −2 modified@@ -1242,15 +1242,21 @@ def delete_view(self, request, object_id, extra_context=None): def history_view(self, request, object_id, extra_context=None): "The 'history' admin view for this model." from django.contrib.admin.models import LogEntry + # First check if the user can see this history. model = self.model + obj = get_object_or_404(model, pk=unquote(object_id)) + + if not self.has_change_permission(request, obj): + raise PermissionDenied + + # Then get the history for this object. opts = model._meta app_label = opts.app_label action_list = LogEntry.objects.filter( object_id = object_id, content_type__id__exact = ContentType.objects.get_for_model(model).id ).select_related().order_by('action_time') - # If no history was found, see whether this object even exists. - obj = get_object_or_404(model, pk=unquote(object_id)) + context = { 'title': _('Change history: %s') % force_unicode(obj), 'action_list': action_list,
tests/regressiontests/admin_views/tests.py+40 −0 modified@@ -827,6 +827,46 @@ def testChangeView(self): self.assertContains(request, 'login-form') self.client.get('/test_admin/admin/logout/') + def testHistoryView(self): + """History view should restrict access.""" + + # add user shoud not be able to view the list of article or change any of them + self.client.get('/test_admin/admin/') + self.client.post('/test_admin/admin/', self.adduser_login) + response = self.client.get('/test_admin/admin/admin_views/article/1/history/') + self.assertEqual(response.status_code, 403) + self.client.get('/test_admin/admin/logout/') + + # change user can view all items and edit them + self.client.get('/test_admin/admin/') + self.client.post('/test_admin/admin/', self.changeuser_login) + response = self.client.get('/test_admin/admin/admin_views/article/1/history/') + self.assertEqual(response.status_code, 200) + + # Test redirection when using row-level change permissions. Refs #11513. + RowLevelChangePermissionModel.objects.create(id=1, name="odd id") + RowLevelChangePermissionModel.objects.create(id=2, name="even id") + for login_dict in [self.super_login, self.changeuser_login, self.adduser_login, self.deleteuser_login]: + self.client.post('/test_admin/admin/', login_dict) + response = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/1/history/') + self.assertEqual(response.status_code, 403) + + response = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/2/history/') + self.assertEqual(response.status_code, 200) + + self.client.get('/test_admin/admin/logout/') + + for login_dict in [self.joepublic_login, self.no_username_login]: + self.client.post('/test_admin/admin/', login_dict) + response = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/1/history/') + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'login-form') + response = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/2/history/') + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'login-form') + + self.client.get('/test_admin/admin/logout/') + def testConditionallyShowAddSectionLink(self): """ The foreign key widget should only show the "add related" button if the
Vulnerability mechanics
Synthesis attempt was rejected by the grounding validator. Re-run pending.
References
10- www.djangoproject.com/weblog/2013/feb/19/security/nvdPatchVendor Advisory
- github.com/advisories/GHSA-r7w6-p47g-vj53ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2013-0305ghsaADVISORY
- rhn.redhat.com/errata/RHSA-2013-0670.htmlnvdWEB
- ubuntu.com/usn/usn-1757-1nvdWEB
- www.debian.org/security/2013/dsa-2634nvdWEB
- github.com/django/django/commit/0e7861aec73702f7933ce2a93056f7983939f0d6ghsaWEB
- github.com/django/django/commit/d3a45e10c8ac8268899999129daa27652ec0da35ghsaWEB
- github.com/pypa/advisory-database/tree/main/vulns/django/PYSEC-2013-16.yamlghsaWEB
- www.djangoproject.com/weblog/2013/feb/19/securityghsaWEB
News mentions
0No linked articles in our index yet.