django CMS: Broken access control in page *Duplicate* allows reading the content of any page (cross-site / restriction bypass)
Impact
The only authorization gate on the duplicate flow is PageAdmin.has_add_permission,
which checks user_can_add_page(user, site) / user_can_add_subpage(...) — i.e. “may
this user create a page at all”. Nothing checks the user’s relationship to the page being
copied:
cms/admin/forms.py—DuplicatePageForm.source = ModelChoiceField(queryset=Page.objects.all(), widget=HiddenInput())spans every page in the database, on every site.cms/admin/forms.py—AddPageForm.__init__returns early when thesourcewidget is hidden, so the queryset is never narrowed to the user’s site/subtree.cms/admin/forms.py—AddPageForm.clean()validates only URL uniqueness;sourceis never validated against the user.cms/admin/pageadmin.py—duplicate()seedssourcefrom the URL only on GET; on POST the value comes entirely from the request body.cms/admin/forms.py—AddPageForm.save()→from_source()performssource.copy(..., permissions=False)and copies every placeholder and all plugins ofsourceinto a new page on the attacker’s site. Becausepermissions=Falsedrops the source’s view restrictions, the resulting copy is fully readable by the attacker.
This crosses a real privilege boundary: a staff user restricted (via CMS_PERMISSION) to
their own site or subtree can exfiltrate the content of restricted pages and of pages
belonging to other tenants.
Read-back is trivial (verified): the copy is created on the attacker’s site and, because
copy(..., permissions=False) strips the source’s view restrictions, the new page is
unrestricted. user_can_view_page() then returns True for it (unrestricted +
PUBLIC_FOR), so the attacker — or even an anonymous visitor — can read the duplicated
content directly from the front end. No further permission on the new page is required.
Proof of concept
- Log in as a staff user
attackerwho has add page permission but no view/change permission on a target (secret / other-site) pageSECRET_ID. - Send (the URL
<id>only needs to be aPageContentthe attacker can already see — e.g. one of their own pages; the victim id goes in the POST body):
POST /admin/cms/pagecontent/<MY_OWN_PAGECONTENT_ID>/duplicate/ HTTP/1.1
Cookie: sessionid=<attacker session>
Content-Type: application/x-www-form-urlencoded
csrfmiddlewaretoken=...&title=x&slug=x&language=en&source=<SECRET_ID>
- A new, unrestricted page is created under the attacker’s site containing a verbatim copy of the secret page’s plugins, which the attacker can now preview/edit/read.
Patches
Enforce an object-level permission check on source:
class DuplicatePageForm(AddPageForm):
source = forms.ModelChoiceField(
queryset=Page.objects.all(),
required=True,
widget=forms.HiddenInput(),
)
def clean_source(self):
source = self.cleaned_data.get("source")
if source and not user_can_view_page(self._user, source):
raise ValidationError(_("You do not have permission to copy this page."))
return source
(user_can_view_page is imported from cms.utils.page_permissions.)
Workarounds
Until patched, restrict access to the cms.add_page permission to fully-trusted staff, or
disable the duplicate action for delegated/limited editors.
References
cms/admin/pageadmin.py—duplicate(),has_add_permission(),get_urls()cms/admin/forms.py—DuplicatePageForm,AddPageForm.__init__/clean/save/from_source- Regression tests:
cms/tests/test_forms.py::DuplicatePageFormSecurityTestCase
Affected software
Click a technology to see all vulnerabilities affecting it.
| Platform | Package | Vulnerable versions | Fixed in |
|---|---|---|---|
| Python (PyPI) | django-cms | < 5.0.9 | 5.0.9 |
References
- https://github.com/django-cms/django-cms/security/advisories/GHSA-6x92-6vx4-5fwr
- https://github.com/django-cms/django-cms/pull/8713
- https://github.com/django-cms/django-cms/commit/3e1ccf7573eb1a74ebbbfaaa812c1f5cadf14e6c
- https://github.com/django-cms/django-cms
- https://github.com/django-cms/django-cms/releases/tag/5.0.9
- https://pypi.org/project/django-cms
- https://github.com/advisories/GHSA-6x92-6vx4-5fwr
- https://nvd.nist.gov/vuln/detail/CVE-2026-63003
- CVSS vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N- Weakness (CWE)
CWE-639, CWE-862- Platform
- Python (PyPI)
- Package
django-cms- Disclosed
- 20.08.2026
- Updated
- 11.10.2026 15:18
Sources and licenses
OSV / GitHub Advisory Database - PYSEC-2026-3821 - PyPA Advisory Database; CC BY 4.0
OSV / GitHub Advisory Database - GHSA-6x92-6vx4-5fwr - CC BY 4.0
Vulnerability history: django-cms
All published vulnerabilities of this package (Python (PyPI)), newest first. Shows whether this is a one-off slip or a package that needs watching.
- Total entries
- 10
- First → latest
- 08.2017 08.2026
- Without a fix
- 0
- Exploited in attacks
- 0
Severity breakdown
- High 2
- Medium 8
Disclosures per year
- 2017: 1
- 2018: 0
- 2019: 0
- 2020: 0
- 2021: 0
- 2022: 1
- 2023: 0
- 2024: 1
- 2025: 0
- 2026: 7
-
django CMS: Broken access control in page *Duplicate* allows reading the content of any page (cross-site / restriction bypass) this entry
Know which vulnerabilities affect your application
With the DockRay integration and software inventory enabled, DockRay checks reported component versions against known vulnerabilities and notifies you about confirmed matches. Coverage depends on the supported technology and available advisory data.