Files
vuln-osv/data/PyPI/GHSA-6x92-6vx4-5fwr.json
T

1 line
6.3 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"schema_version":"1.9.0","id":"GHSA-6x92-6vx4-5fwr","published":"2026-08-20T18:42:44Z","modified":"2026-09-10T12:25:36.931395327Z","aliases":["CVE-2026-63003","PYSEC-2026-3821"],"summary":"django CMS: Broken access control in page *Duplicate* allows reading the content of any page (cross-site / restriction bypass)","details":"## Impact\n\nThe only authorization gate on the duplicate flow is `PageAdmin.has_add_permission`,\nwhich checks `user_can_add_page(user, site)` / `user_can_add_subpage(...)` — i.e. *“may\nthis user create a page at all”*. Nothing checks the user’s relationship to the page being\ncopied:\n\n- `cms/admin/forms.py` — `DuplicatePageForm.source = ModelChoiceField(queryset=Page.objects.all(), widget=HiddenInput())`\n spans **every page in the database, on every site**.\n- `cms/admin/forms.py` — `AddPageForm.__init__` returns early when the `source` widget is\n hidden, so the queryset is **never narrowed** to the user’s site/subtree.\n- `cms/admin/forms.py` — `AddPageForm.clean()` validates only URL uniqueness; `source` is\n never validated against the user.\n- `cms/admin/pageadmin.py` — `duplicate()` seeds `source` from the URL **only on GET**; on\n POST the value comes entirely from the request body.\n- `cms/admin/forms.py` — `AddPageForm.save()` → `from_source()` performs\n `source.copy(..., permissions=False)` and copies **every placeholder and all plugins** of\n `source` into a new page on the attacker’s site. Because `permissions=False` drops the\n source’s view restrictions, the resulting copy is fully readable by the attacker.\n\nThis crosses a real privilege boundary: a staff user restricted (via `CMS_PERMISSION`) to\ntheir own site or subtree can exfiltrate the content of restricted pages and of pages\nbelonging to other tenants.\n\n**Read-back is trivial (verified):** the copy is created on the attacker’s site and, because\n`copy(..., permissions=False)` strips the source’s view restrictions, the new page is\n*unrestricted*. `user_can_view_page()` then returns `True` for it (unrestricted +\n`PUBLIC_FOR`), so the attacker — or even an anonymous visitor — can read the duplicated\ncontent directly from the front end. No further permission on the new page is required.\n\n## Proof of concept\n\n1. Log in as a staff user `attacker` who has *add page* permission but **no** view/change\n permission on a target (secret / other-site) page `SECRET_ID`.\n2. Send (the URL `<id>` only needs to be a `PageContent` the attacker can already see —\n e.g. one of their own pages; the victim id goes in the POST body):\n\n```http\nPOST /admin/cms/pagecontent/<MY_OWN_PAGECONTENT_ID>/duplicate/ HTTP/1.1\nCookie: sessionid=<attacker session>\nContent-Type: application/x-www-form-urlencoded\n\ncsrfmiddlewaretoken=...&title=x&slug=x&language=en&source=<SECRET_ID>\n```\n\n3. A new, unrestricted page is created under the attacker’s site containing a verbatim\n copy of the secret page’s plugins, which the attacker can now preview/edit/read.\n\n## Patches \n\nEnforce an object-level permission check on `source`:\n\n```python\nclass DuplicatePageForm(AddPageForm):\n source = forms.ModelChoiceField(\n queryset=Page.objects.all(),\n required=True,\n widget=forms.HiddenInput(),\n )\n\n def clean_source(self):\n source = self.cleaned_data.get(\"source\")\n if source and not user_can_view_page(self._user, source):\n raise ValidationError(_(\"You do not have permission to copy this page.\"))\n return source\n```\n\n(`user_can_view_page` is imported from `cms.utils.page_permissions`.)\n\n## Workarounds\n\nUntil patched, restrict access to the `cms.add_page` permission to fully-trusted staff, or\ndisable the duplicate action for delegated/limited editors.\n\n## References\n\n- `cms/admin/pageadmin.py` — `duplicate()`, `has_add_permission()`, `get_urls()`\n- `cms/admin/forms.py` — `DuplicatePageForm`, `AddPageForm.__init__/clean/save/from_source`\n- Regression tests: `cms/tests/test_forms.py::DuplicatePageFormSecurityTestCase`","affected":[{"package":{"name":"django-cms","ecosystem":"PyPI","purl":"pkg:pypi/django-cms"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"5.0.9"}]}],"versions":["2.0.1","2.0.2","2.1.1","2.1.2","2.1.3","2.1.4","2.1.5","2.2","2.2.1","2.3.1","2.3.2","2.3.3","2.3.4","2.3.5","2.3.6","2.3.7","2.3.8","2.4.0","2.4.1","2.4.2","2.4.3","3.0","3.0.1","3.0.10","3.0.11","3.0.12","3.0.13","3.0.14","3.0.15","3.0.16","3.0.17","3.0.18","3.0.2","3.0.3","3.0.4","3.0.5","3.0.6","3.0.7","3.0.8","3.0.9","3.1.0","3.1.0.b1","3.1.0rc1","3.1.0rc2","3.1.1","3.1.2","3.1.3","3.1.4","3.1.5","3.1.6","3.1.7","3.10.0","3.10.0rc1","3.10.0rc2","3.10.1","3.10.1rc1","3.11.0","3.11.1","3.11.10","3.11.11","3.11.1rc1","3.11.2","3.11.3","3.11.4","3.11.5","3.11.6","3.11.7","3.11.8","3.11.9","3.2.0","3.2.1","3.2.2","3.2.3","3.2.4","3.2.5","3.3.0","3.3.0rc2","3.3.0rc3","3.3.0rc4","3.3.1","3.3.2","3.3.3","3.3.4","3.4.0","3.4.1","3.4.2","3.4.3","3.4.4","3.4.5","3.4.6","3.4.7","3.5.0","3.5.1","3.5.2","3.5.3","3.5.4","3.6.0","3.6.0rc1","3.6.0rc3","3.6.1","3.7.0","3.7.0rc1","3.7.0rc2","3.7.1","3.7.2","3.7.3","3.7.4","3.8.0","3.8.0rc1","3.8.1","3.8.2","3.9.0","3.9.0rc1","3.9.0rc2","3.9.0rc3","4.1.0","4.1.0rc1","4.1.0rc2","4.1.0rc3","4.1.0rc4","4.1.0rc5","4.1.1","4.1.10","4.1.11","4.1.2","4.1.3","4.1.4","4.1.5","4.1.6","4.1.7","4.1.9","5.0.0","5.0.0a1","5.0.1","5.0.2","5.0.3","5.0.4","5.0.5","5.0.6","5.0.7","5.0.8"],"database_specific":{"last_known_affected_version_range":"<= 5.0.8","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/08/GHSA-6x92-6vx4-5fwr/GHSA-6x92-6vx4-5fwr.json"}}],"references":[{"type":"WEB","url":"https://github.com/django-cms/django-cms/security/advisories/GHSA-6x92-6vx4-5fwr"},{"type":"WEB","url":"https://github.com/django-cms/django-cms/pull/8713"},{"type":"WEB","url":"https://github.com/django-cms/django-cms/commit/3e1ccf7573eb1a74ebbbfaaa812c1f5cadf14e6c"},{"type":"PACKAGE","url":"https://github.com/django-cms/django-cms"},{"type":"WEB","url":"https://github.com/django-cms/django-cms/releases/tag/5.0.9"}],"database_specific":{"cwe_ids":["CWE-639","CWE-862"],"github_reviewed":true,"github_reviewed_at":"2026-08-20T18:42:44Z","nvd_published_at":null,"severity":"MODERATE"},"severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N"}]}