High severity8.8NVD Advisory· Published Aug 18, 2017· Updated May 13, 2026
CVE-2015-5081
CVE-2015-5081
Description
Cross-site request forgery (CSRF) vulnerability in django CMS before 3.0.14, 3.1.x before 3.1.1 allows remote attackers to manipulate privileged users into performing unknown actions via unspecified vectors.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
django-cmsPyPI | < 3.0.14 | 3.0.14 |
django-cmsPyPI | >= 3.1.0b1, < 3.1.1 | 3.1.1 |
Affected products
2cpe:2.3:a:django-cms:django_cms:*:*:*:*:*:*:*:*+ 1 more
- cpe:2.3:a:django-cms:django_cms:*:*:*:*:*:*:*:*range: <=3.0.13
- cpe:2.3:a:django-cms:django_cms:3.1:*:*:*:*:*:*:*
Patches
2f77cbc607d6eMerge pull request #4218 from divio/issues/merge_csrf_fix
7 files changed · +51 −12
cms/admin/pageadmin.py+6 −0 modified@@ -928,6 +928,7 @@ def change_template(self, request, object_id): helpers.make_revision_with_plugins(page, request.user, message) return HttpResponse(force_unicode(_("The template was successfully changed"))) + @require_POST @wrap_transaction def move_page(self, request, page_id, extra_context=None): """ @@ -1013,6 +1014,7 @@ def copy_language(self, request, page_id): helpers.make_revision_with_plugins(page, request.user, message) return HttpResponse("ok") + @require_POST @wrap_transaction def copy_page(self, request, page_id, extra_context=None): """ @@ -1046,6 +1048,7 @@ def copy_page(self, request, page_id, extra_context=None): context.update(extra_context or {}) return HttpResponseRedirect('../../') + @require_POST @wrap_transaction @create_revision() def publish_page(self, request, page_id, language): @@ -1146,6 +1149,7 @@ def cleanup_history(self, page, publish=False): revision.delete() deleted.append(revision.pk) + @require_POST @wrap_transaction def unpublish(self, request, page_id, language): """ @@ -1181,6 +1185,7 @@ def unpublish(self, request, page_id, language): path = "%s?language=%s&page_id=%s" % (path, request.GET.get('redirect_language'), request.GET.get('redirect_page_id')) return HttpResponseRedirect(path) + @require_POST @wrap_transaction def revert_page(self, request, page_id, language): page = get_object_or_404(Page, id=page_id) @@ -1316,6 +1321,7 @@ def preview_page(self, request, object_id, language): page.site.domain, url) return HttpResponseRedirect(url) + @require_POST def change_innavigation(self, request, page_id): """ Switch the in_navigation of a page
cms/static/cms/js/modules/cms.changelist.js+11 −2 modified@@ -180,8 +180,17 @@ $(document).ready(function () { // cancel if not confirmed if(!confirm(that.options.lang.publish.replace('§', $(this).text().toLowerCase()))) return false; - // publish page and update - window.location.href = $(this).attr('href'); + // send post request to prevent xss attacks + $.ajax({ + 'type': 'post', + 'url': $(this).prop('href'), + 'success': function () { + CMS.API.Helpers.reloadBrowser(); + }, + 'error': function (request) { + throw new Error(request); + } + }); }); },
cms/static/cms/js/modules/cms.toolbar.js+21 −1 modified@@ -213,7 +213,27 @@ $(document).ready(function () { // in case of the publish button btn.find('.cms_publish-page').bind(that.click, function (e) { - if(!confirm(that.config.lang.publish)) e.preventDefault(); + if(!confirm(that.config.lang.publish)) { + e.preventDefault(); + } + }); + + btn.find('.cms_btn-publish').bind(that.click, function (e) { + e.preventDefault(); + // send post request to prevent xss attacks + $.ajax({ + 'type': 'post', + 'url': $(this).prop('href'), + 'data': { + 'csrfmiddlewaretoken': CMS.config.csrf + }, + 'success': function () { + CMS.API.Helpers.reloadBrowser(); + }, + 'error': function (request) { + throw new Error(request); + } + }); }); }); },
cms/templates/admin/cms/page/tree/menu_item.html+2 −2 modified@@ -30,8 +30,8 @@ {% if lang in page.languages %} <div class="language-tooltip" hidden="hidden"> {% trans "Pick an action:" %} - <a href="./{{ page.id }}/{{ lang }}/unpublish/?redirect_language={{ preview_language }}&redirect_page_id={{ request.GET.page_id }}">{% trans "Unpublish" %}</a> - <a href="./{{ page.id }}/{{ lang }}/publish/?redirect_language={{ preview_language }}&redirect_page_id={{ request.GET.page_id }}">{% trans "Publish" %}</a> + <a href="./{{ page.id }}/{{ lang }}/unpublish/?redirect_language={{ preview_language }}&redirect_page_id={{ request.GET.page_id }}" class="js-ajax-submit">{% trans "Unpublish" %}</a> + <a href="./{{ page.id }}/{{ lang }}/publish/?redirect_language={{ preview_language }}&redirect_page_id={{ request.GET.page_id }}" class="js-ajax-submit">{% trans "Publish" %}</a> </div> {% endif %} {% else %}
cms/tests/admin.py+7 −3 modified@@ -704,14 +704,14 @@ def test_change_publish_unpublish(self): with self.login_user_context(permless): request = self.get_request() response = self.admin_class.publish_page(request, page.pk, "en") - self.assertEqual(response.status_code, 403) + self.assertEqual(response.status_code, 405) page = self.reload(page) self.assertFalse(page.is_published('en')) request = self.get_request(post_data={'no': 'data'}) response = self.admin_class.publish_page(request, page.pk, "en") - # Forbidden self.assertEqual(response.status_code, 403) + page = self.reload(page) self.assertFalse(page.is_published('en')) admin_user = self.get_admin() @@ -747,6 +747,10 @@ def test_change_innavigation(self): with self.login_user_context(permless): request = self.get_request() response = self.admin_class.change_innavigation(request, page.pk) + self.assertEqual(response.status_code, 405) + with self.login_user_context(permless): + request = self.get_request(post_data={'no': 'data'}) + response = self.admin_class.change_innavigation(request, page.pk) self.assertEqual(response.status_code, 403) with self.login_user_context(permless): request = self.get_request(post_data={'no': 'data'}) @@ -806,7 +810,7 @@ def test_revert_page_redirects(self): admin_user = self.get_admin() self.page.publish("en") # Ensure public copy exists before reverting with self.login_user_context(admin_user): - response = self.client.get(admin_reverse('cms_page_revert_page', args=(self.page.pk, 'en'))) + response = self.client.post(admin_reverse('cms_page_revert_page', args=(self.page.pk, 'en'))) self.assertEqual(response.status_code, 302) url = response['Location'] self.assertTrue(url.endswith('?%s' % get_cms_setting('CMS_TOOLBAR_URL__EDIT_OFF')))
cms/tests/publisher.py+3 −3 modified@@ -342,7 +342,7 @@ def test_publish_home(self): self.assertEqual(Page.objects.all().count(), 1) superuser = self.get_superuser() with self.login_user_context(superuser): - response = self.client.get(admin_reverse("cms_page_publish_page", args=[page.pk, 'en'])) + response = self.client.post(admin_reverse("cms_page_publish_page", args=[page.pk, 'en'])) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], "http://testserver/en/?%s" % get_cms_setting('CMS_TOOLBAR_URL__EDIT_OFF')) @@ -381,7 +381,7 @@ def test_publish_admin(self): page = self.create_page("test_admin", published=False) superuser = self.get_superuser() with self.login_user_context(superuser): - response = self.client.get(admin_reverse("cms_page_publish_page", args=[page.pk, 'en'])) + response = self.client.post(admin_reverse("cms_page_publish_page", args=[page.pk, 'en'])) self.assertEqual(response.status_code, 302) page = Page.objects.get(pk=page.pk) @@ -396,7 +396,7 @@ def test_publish_wrong_lang(self): ): with self.login_user_context(superuser): with force_language('de'): - response = self.client.get(admin_reverse("cms_page_publish_page", args=[page.pk, 'en'])) + response = self.client.post(admin_reverse("cms_page_publish_page", args=[page.pk, 'en'])) self.assertEqual(response.status_code, 302) page = Page.objects.get(pk=page.pk)
cms/tests/reversion_tests.py+1 −1 modified@@ -246,7 +246,7 @@ def test_publish_limits(self): self.assertEqual(Revision.objects.all().count(), 5) for x in range(10): publish_url = URL_CMS_PAGE + "%s/en/publish/" % page_pk - response = self.client.get(publish_url) + response = self.client.post(publish_url) self.assertEqual(response.status_code, 302) self.assertEqual(Revision.objects.all().count(), 4)
f77cbc607d6eMerge pull request #4218 from divio/issues/merge_csrf_fix
7 files changed · +51 −12
cms/admin/pageadmin.py+6 −0 modified@@ -928,6 +928,7 @@ def change_template(self, request, object_id): helpers.make_revision_with_plugins(page, request.user, message) return HttpResponse(force_unicode(_("The template was successfully changed"))) + @require_POST @wrap_transaction def move_page(self, request, page_id, extra_context=None): """ @@ -1013,6 +1014,7 @@ def copy_language(self, request, page_id): helpers.make_revision_with_plugins(page, request.user, message) return HttpResponse("ok") + @require_POST @wrap_transaction def copy_page(self, request, page_id, extra_context=None): """ @@ -1046,6 +1048,7 @@ def copy_page(self, request, page_id, extra_context=None): context.update(extra_context or {}) return HttpResponseRedirect('../../') + @require_POST @wrap_transaction @create_revision() def publish_page(self, request, page_id, language): @@ -1146,6 +1149,7 @@ def cleanup_history(self, page, publish=False): revision.delete() deleted.append(revision.pk) + @require_POST @wrap_transaction def unpublish(self, request, page_id, language): """ @@ -1181,6 +1185,7 @@ def unpublish(self, request, page_id, language): path = "%s?language=%s&page_id=%s" % (path, request.GET.get('redirect_language'), request.GET.get('redirect_page_id')) return HttpResponseRedirect(path) + @require_POST @wrap_transaction def revert_page(self, request, page_id, language): page = get_object_or_404(Page, id=page_id) @@ -1316,6 +1321,7 @@ def preview_page(self, request, object_id, language): page.site.domain, url) return HttpResponseRedirect(url) + @require_POST def change_innavigation(self, request, page_id): """ Switch the in_navigation of a page
cms/static/cms/js/modules/cms.changelist.js+11 −2 modified@@ -180,8 +180,17 @@ $(document).ready(function () { // cancel if not confirmed if(!confirm(that.options.lang.publish.replace('§', $(this).text().toLowerCase()))) return false; - // publish page and update - window.location.href = $(this).attr('href'); + // send post request to prevent xss attacks + $.ajax({ + 'type': 'post', + 'url': $(this).prop('href'), + 'success': function () { + CMS.API.Helpers.reloadBrowser(); + }, + 'error': function (request) { + throw new Error(request); + } + }); }); },
cms/static/cms/js/modules/cms.toolbar.js+21 −1 modified@@ -213,7 +213,27 @@ $(document).ready(function () { // in case of the publish button btn.find('.cms_publish-page').bind(that.click, function (e) { - if(!confirm(that.config.lang.publish)) e.preventDefault(); + if(!confirm(that.config.lang.publish)) { + e.preventDefault(); + } + }); + + btn.find('.cms_btn-publish').bind(that.click, function (e) { + e.preventDefault(); + // send post request to prevent xss attacks + $.ajax({ + 'type': 'post', + 'url': $(this).prop('href'), + 'data': { + 'csrfmiddlewaretoken': CMS.config.csrf + }, + 'success': function () { + CMS.API.Helpers.reloadBrowser(); + }, + 'error': function (request) { + throw new Error(request); + } + }); }); }); },
cms/templates/admin/cms/page/tree/menu_item.html+2 −2 modified@@ -30,8 +30,8 @@ {% if lang in page.languages %} <div class="language-tooltip" hidden="hidden"> {% trans "Pick an action:" %} - <a href="./{{ page.id }}/{{ lang }}/unpublish/?redirect_language={{ preview_language }}&redirect_page_id={{ request.GET.page_id }}">{% trans "Unpublish" %}</a> - <a href="./{{ page.id }}/{{ lang }}/publish/?redirect_language={{ preview_language }}&redirect_page_id={{ request.GET.page_id }}">{% trans "Publish" %}</a> + <a href="./{{ page.id }}/{{ lang }}/unpublish/?redirect_language={{ preview_language }}&redirect_page_id={{ request.GET.page_id }}" class="js-ajax-submit">{% trans "Unpublish" %}</a> + <a href="./{{ page.id }}/{{ lang }}/publish/?redirect_language={{ preview_language }}&redirect_page_id={{ request.GET.page_id }}" class="js-ajax-submit">{% trans "Publish" %}</a> </div> {% endif %} {% else %}
cms/tests/admin.py+7 −3 modified@@ -704,14 +704,14 @@ def test_change_publish_unpublish(self): with self.login_user_context(permless): request = self.get_request() response = self.admin_class.publish_page(request, page.pk, "en") - self.assertEqual(response.status_code, 403) + self.assertEqual(response.status_code, 405) page = self.reload(page) self.assertFalse(page.is_published('en')) request = self.get_request(post_data={'no': 'data'}) response = self.admin_class.publish_page(request, page.pk, "en") - # Forbidden self.assertEqual(response.status_code, 403) + page = self.reload(page) self.assertFalse(page.is_published('en')) admin_user = self.get_admin() @@ -747,6 +747,10 @@ def test_change_innavigation(self): with self.login_user_context(permless): request = self.get_request() response = self.admin_class.change_innavigation(request, page.pk) + self.assertEqual(response.status_code, 405) + with self.login_user_context(permless): + request = self.get_request(post_data={'no': 'data'}) + response = self.admin_class.change_innavigation(request, page.pk) self.assertEqual(response.status_code, 403) with self.login_user_context(permless): request = self.get_request(post_data={'no': 'data'}) @@ -806,7 +810,7 @@ def test_revert_page_redirects(self): admin_user = self.get_admin() self.page.publish("en") # Ensure public copy exists before reverting with self.login_user_context(admin_user): - response = self.client.get(admin_reverse('cms_page_revert_page', args=(self.page.pk, 'en'))) + response = self.client.post(admin_reverse('cms_page_revert_page', args=(self.page.pk, 'en'))) self.assertEqual(response.status_code, 302) url = response['Location'] self.assertTrue(url.endswith('?%s' % get_cms_setting('CMS_TOOLBAR_URL__EDIT_OFF')))
cms/tests/publisher.py+3 −3 modified@@ -342,7 +342,7 @@ def test_publish_home(self): self.assertEqual(Page.objects.all().count(), 1) superuser = self.get_superuser() with self.login_user_context(superuser): - response = self.client.get(admin_reverse("cms_page_publish_page", args=[page.pk, 'en'])) + response = self.client.post(admin_reverse("cms_page_publish_page", args=[page.pk, 'en'])) self.assertEqual(response.status_code, 302) self.assertEqual(response['Location'], "http://testserver/en/?%s" % get_cms_setting('CMS_TOOLBAR_URL__EDIT_OFF')) @@ -381,7 +381,7 @@ def test_publish_admin(self): page = self.create_page("test_admin", published=False) superuser = self.get_superuser() with self.login_user_context(superuser): - response = self.client.get(admin_reverse("cms_page_publish_page", args=[page.pk, 'en'])) + response = self.client.post(admin_reverse("cms_page_publish_page", args=[page.pk, 'en'])) self.assertEqual(response.status_code, 302) page = Page.objects.get(pk=page.pk) @@ -396,7 +396,7 @@ def test_publish_wrong_lang(self): ): with self.login_user_context(superuser): with force_language('de'): - response = self.client.get(admin_reverse("cms_page_publish_page", args=[page.pk, 'en'])) + response = self.client.post(admin_reverse("cms_page_publish_page", args=[page.pk, 'en'])) self.assertEqual(response.status_code, 302) page = Page.objects.get(pk=page.pk)
cms/tests/reversion_tests.py+1 −1 modified@@ -246,7 +246,7 @@ def test_publish_limits(self): self.assertEqual(Revision.objects.all().count(), 5) for x in range(10): publish_url = URL_CMS_PAGE + "%s/en/publish/" % page_pk - response = self.client.get(publish_url) + response = self.client.post(publish_url) self.assertEqual(response.status_code, 302) self.assertEqual(Revision.objects.all().count(), 4)
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- www.openwall.com/lists/oss-security/2015/06/28/1nvdMailing ListThird Party AdvisoryWEB
- github.com/advisories/GHSA-2pqc-gv8q-pvqvghsaADVISORY
- github.com/divio/django-cms/commit/f77cbc607d6e2a62e63287d37ad320109a2cc78anvdThird Party AdvisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2015-5081ghsaADVISORY
- www.django-cms.org/en/blog/2015/06/27/311-3014-release/nvdRelease NotesVendor Advisory
- github.com/django-cms/django-cms/commit/f77cbc607d6e2a62e63287d37ad320109a2cc78aghsaWEB
- github.com/pypa/advisory-database/tree/main/vulns/django-cms/PYSEC-2017-11.yamlghsaWEB
- www.django-cms.org/en/blog/2015/06/27/311-3014-releaseghsaWEB
News mentions
0No linked articles in our index yet.