Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b04b208a81 | ||
|
|
d55003d058 | ||
|
|
05f1de75a9 | ||
|
|
9497bfe341 | ||
|
|
c77e6df6fe | ||
|
|
e1fc2a5963 |
@@ -1,3 +1,11 @@
|
||||
=== 3.4.3 (2017-04-24) ===
|
||||
|
||||
* Fixed a security vulnerability in the page redirect field which allowed users
|
||||
to insert JavaScript code.
|
||||
* Fixed a security vulnerability where the ``next`` parameter for the toolbar login
|
||||
was not sanitised and could point to another domain.
|
||||
|
||||
|
||||
=== 3.4.2 (2017-01-23) ===
|
||||
|
||||
* Escaped strings in ``close_frame`` JS template.
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
__version__ = '3.4.2'
|
||||
__version__ = '3.4.3'
|
||||
|
||||
default_app_config = 'cms.apps.CMSConfig'
|
||||
|
||||
@@ -5,6 +5,7 @@ from django.forms.fields import EMPTY_VALUES
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from cms.forms.utils import get_site_choices, get_page_choices
|
||||
from cms.forms.validators import validate_url
|
||||
from cms.forms.widgets import PageSelectWidget, PageSmartLinkWidget
|
||||
from cms.models.pagemodel import Page
|
||||
|
||||
@@ -75,8 +76,10 @@ class PageSelectFormField(forms.MultiValueField):
|
||||
def _has_changed(self, initial, data):
|
||||
return self.has_changed(initial, data)
|
||||
|
||||
|
||||
class PageSmartLinkField(forms.CharField):
|
||||
widget = PageSmartLinkWidget
|
||||
default_validators = [validate_url]
|
||||
|
||||
def __init__(self, max_length=None, min_length=None, placeholder_text=None,
|
||||
ajax_view=None, *args, **kwargs):
|
||||
@@ -89,3 +92,7 @@ class PageSmartLinkField(forms.CharField):
|
||||
attrs = super(PageSmartLinkField, self).widget_attrs(widget)
|
||||
attrs.update({'placeholder_text': self.placeholder_text})
|
||||
return attrs
|
||||
|
||||
def clean(self, value):
|
||||
value = self.to_python(value).strip()
|
||||
return super(PageSmartLinkField, self).clean(value)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import RegexValidator, URLValidator
|
||||
|
||||
from cms.utils.urlutils import relative_url_regex
|
||||
|
||||
|
||||
def validate_url(value):
|
||||
try:
|
||||
# Validate relative urls first
|
||||
RegexValidator(regex=relative_url_regex)(value)
|
||||
except ValidationError:
|
||||
# Fallback to absolute urls
|
||||
URLValidator()(value)
|
||||
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -1,3 +1,3 @@
|
||||
/*!
|
||||
* @copyright: https://github.com/divio/django-cms
|
||||
*/@font-face{font-family:django-cms-iconfont;src:url(../../fonts/3.4.2/django-cms-iconfont.eot);src:url(../../fonts/3.4.2/django-cms-iconfont.eot#iefix) format("eot"),url(../../fonts/3.4.2/django-cms-iconfont.woff2) format("woff2"),url(../../fonts/3.4.2/django-cms-iconfont.woff) format("woff"),url(../../fonts/3.4.2/django-cms-iconfont.ttf) format("truetype"),url(../../fonts/3.4.2/django-cms-iconfont.svg#django-cms-iconfont) format("svg");font-weight:400;font-style:normal}.cms-icon{display:inline-block;font:normal normal normal 16px/1 django-cms-iconfont;text-rendering:auto;-webkit-transform:translate(0,0);transform:translate(0,0);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.cms-icon-alias:before{content:"\E001"}.cms-icon-arrow-right:before{content:"\E002"}.cms-icon-arrow-wide:before{content:"\E003"}.cms-icon-arrow:before{content:"\E004"}.cms-icon-bin:before{content:"\E005"}.cms-icon-check-o:before{content:"\E006"}.cms-icon-check-square:before{content:"\E007"}.cms-icon-check:before{content:"\E008"}.cms-icon-close:before{content:"\E009"}.cms-icon-cogs:before{content:"\E00A"}.cms-icon-copy:before{content:"\E00B"}.cms-icon-eye:before{content:"\E00C"}.cms-icon-forbidden:before{content:"\E00D"}.cms-icon-handler:before{content:"\E00E"}.cms-icon-home:before{content:"\E00F"}.cms-icon-info:before{content:"\E010"}.cms-icon-loader:before{content:"\E011"}.cms-icon-lock:before{content:"\E012"}.cms-icon-logo:before{content:"\E013"}.cms-icon-menu:before{content:"\E014"}.cms-icon-minimize:before{content:"\E015"}.cms-icon-minus-square-o:before{content:"\E016"}.cms-icon-minus-square:before{content:"\E017"}.cms-icon-minus:before{content:"\E018"}.cms-icon-paste:before{content:"\E019"}.cms-icon-pencil:before{content:"\E01A"}.cms-icon-pin:before{content:"\E01B"}.cms-icon-plus-square-o:before{content:"\E01C"}.cms-icon-plus:before{content:"\E01D"}.cms-icon-puzzle:before{content:"\E01E"}.cms-icon-scissors:before{content:"\E01F"}.cms-icon-search:before{content:"\E020"}.cms-icon-sitemap:before{content:"\E021"}.cms-icon-squares:before{content:"\E022"}.cms-icon-window:before{content:"\E023"}.cms-welcome-bg{background:#f2f2f2}.cms-welcome{color:#454545;width:60%;min-width:320px;margin:0 auto}.cms-welcome a{color:#0bf}.cms-welcome a:active,.cms-welcome a:focus,.cms-welcome a:hover{text-decoration:underline}.cms-welcome .cms-hidden{display:none}.cms-welcome .cms-welcome-footer,.cms-welcome .cms-welcome-section{box-sizing:border-box;width:60%;min-width:320px;margin:0 auto}.cms-welcome .cms-welcome-header{text-align:center}.cms-welcome .cms-welcome-heading{color:#999;font-size:22px;font-weight:200;text-align:center;padding:40px 0 15px;border-bottom:1px solid #ddd}.cms-welcome .cms-welcome-heading .cms-icon{color:#693}.cms-welcome .cms-welcome-logo,.cms-welcome .cms-welcome-logo:focus,.cms-welcome .cms-welcome-logo:hover{display:inline-block;background-image:url(../../fonts/src/logo.svg);background-repeat:no-repeat;background-position:center center;height:35px;width:200px;font-size:35px;text-decoration:none;margin:50px auto 30px}.cms-welcome .cms-welcome-section{box-shadow:0 0 5px #ddd;padding:20px;margin-bottom:5px;background:#fff}.cms-welcome .cms-welcome-section h2{font-size:16px;font-weight:700;margin:0 0 15px}.cms-welcome .cms-welcome-section p{padding:5px 0}.cms-welcome .cms-welcome-links{padding:10px 5px;margin:0 0 15px;border-bottom:1px solid #ddd}.cms-welcome .cms-welcome-links a{padding:0 5px}.cms-welcome .cms-welcome-notes{font-size:12px;line-height:16px;color:#999;padding:5px 10px}
|
||||
*/@font-face{font-family:django-cms-iconfont;src:url(../../fonts/3.4.3/django-cms-iconfont.eot);src:url(../../fonts/3.4.3/django-cms-iconfont.eot#iefix) format("eot"),url(../../fonts/3.4.3/django-cms-iconfont.woff2) format("woff2"),url(../../fonts/3.4.3/django-cms-iconfont.woff) format("woff"),url(../../fonts/3.4.3/django-cms-iconfont.ttf) format("truetype"),url(../../fonts/3.4.3/django-cms-iconfont.svg#django-cms-iconfont) format("svg");font-weight:400;font-style:normal}.cms-icon{display:inline-block;font:normal normal normal 16px/1 django-cms-iconfont;text-rendering:auto;-webkit-transform:translate(0,0);transform:translate(0,0);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.cms-icon-alias:before{content:"\E001"}.cms-icon-arrow-right:before{content:"\E002"}.cms-icon-arrow-wide:before{content:"\E003"}.cms-icon-arrow:before{content:"\E004"}.cms-icon-bin:before{content:"\E005"}.cms-icon-check-o:before{content:"\E006"}.cms-icon-check-square:before{content:"\E007"}.cms-icon-check:before{content:"\E008"}.cms-icon-close:before{content:"\E009"}.cms-icon-cogs:before{content:"\E00A"}.cms-icon-copy:before{content:"\E00B"}.cms-icon-eye:before{content:"\E00C"}.cms-icon-forbidden:before{content:"\E00D"}.cms-icon-handler:before{content:"\E00E"}.cms-icon-home:before{content:"\E00F"}.cms-icon-info:before{content:"\E010"}.cms-icon-loader:before{content:"\E011"}.cms-icon-lock:before{content:"\E012"}.cms-icon-logo:before{content:"\E013"}.cms-icon-menu:before{content:"\E014"}.cms-icon-minimize:before{content:"\E015"}.cms-icon-minus-square-o:before{content:"\E016"}.cms-icon-minus-square:before{content:"\E017"}.cms-icon-minus:before{content:"\E018"}.cms-icon-paste:before{content:"\E019"}.cms-icon-pencil:before{content:"\E01A"}.cms-icon-pin:before{content:"\E01B"}.cms-icon-plus-square-o:before{content:"\E01C"}.cms-icon-plus:before{content:"\E01D"}.cms-icon-puzzle:before{content:"\E01E"}.cms-icon-scissors:before{content:"\E01F"}.cms-icon-search:before{content:"\E020"}.cms-icon-sitemap:before{content:"\E021"}.cms-icon-squares:before{content:"\E022"}.cms-icon-window:before{content:"\E023"}.cms-welcome-bg{background:#f2f2f2}.cms-welcome{color:#454545;width:60%;min-width:320px;margin:0 auto}.cms-welcome a{color:#0bf}.cms-welcome a:active,.cms-welcome a:focus,.cms-welcome a:hover{text-decoration:underline}.cms-welcome .cms-hidden{display:none}.cms-welcome .cms-welcome-footer,.cms-welcome .cms-welcome-section{box-sizing:border-box;width:60%;min-width:320px;margin:0 auto}.cms-welcome .cms-welcome-header{text-align:center}.cms-welcome .cms-welcome-heading{color:#999;font-size:22px;font-weight:200;text-align:center;padding:40px 0 15px;border-bottom:1px solid #ddd}.cms-welcome .cms-welcome-heading .cms-icon{color:#693}.cms-welcome .cms-welcome-logo,.cms-welcome .cms-welcome-logo:focus,.cms-welcome .cms-welcome-logo:hover{display:inline-block;background-image:url(../../fonts/src/logo.svg);background-repeat:no-repeat;background-position:center center;height:35px;width:200px;font-size:35px;text-decoration:none;margin:50px auto 30px}.cms-welcome .cms-welcome-section{box-shadow:0 0 5px #ddd;padding:20px;margin-bottom:5px;background:#fff}.cms-welcome .cms-welcome-section h2{font-size:16px;font-weight:700;margin:0 0 15px}.cms-welcome .cms-welcome-section p{padding:5px 0}.cms-welcome .cms-welcome-links{padding:10px 5px;margin:0 0 15px;border-bottom:1px solid #ddd}.cms-welcome .cms-welcome-links a{padding:0 5px}.cms-welcome .cms-welcome-notes{font-size:12px;line-height:16px;color:#999;padding:5px 10px}
|
||||
Binary file not shown.
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 114 KiB |
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
@@ -4,12 +4,12 @@
|
||||
// default font file generated by gulp
|
||||
@font-face {
|
||||
font-family: "django-cms-iconfont";
|
||||
src: url("../../fonts/3.4.2/django-cms-iconfont.eot");
|
||||
src: url("../../fonts/3.4.2/django-cms-iconfont.eot#iefix") format("eot"),
|
||||
url("../../fonts/3.4.2/django-cms-iconfont.woff2") format("woff2"),
|
||||
url("../../fonts/3.4.2/django-cms-iconfont.woff") format("woff"),
|
||||
url("../../fonts/3.4.2/django-cms-iconfont.ttf") format("truetype"),
|
||||
url("../../fonts/3.4.2/django-cms-iconfont.svg#django-cms-iconfont") format("svg");
|
||||
src: url("../../fonts/3.4.3/django-cms-iconfont.eot");
|
||||
src: url("../../fonts/3.4.3/django-cms-iconfont.eot#iefix") format("eot"),
|
||||
url("../../fonts/3.4.3/django-cms-iconfont.woff2") format("woff2"),
|
||||
url("../../fonts/3.4.3/django-cms-iconfont.woff") format("woff"),
|
||||
url("../../fonts/3.4.3/django-cms-iconfont.ttf") format("truetype"),
|
||||
url("../../fonts/3.4.3/django-cms-iconfont.svg#django-cms-iconfont") format("svg");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@@ -287,6 +287,44 @@ class PageTest(PageTestBase):
|
||||
self.client.post(endpoint, page_data)
|
||||
self.assertTrue(page.reload().is_dirty('en'), change_message.format(field))
|
||||
|
||||
def test_page_redirect_field_validation(self):
|
||||
superuser = self.get_superuser()
|
||||
data = self.get_new_page_data()
|
||||
|
||||
with self.login_user_context(superuser):
|
||||
self.client.post(URL_CMS_PAGE_ADD, data)
|
||||
|
||||
page = Page.objects.get(title_set__slug=data['slug'], publisher_is_draft=True)
|
||||
|
||||
endpoint = URL_CMS_PAGE_ADVANCED_CHANGE % page.pk
|
||||
redirect_to = URL_CMS_PAGE
|
||||
|
||||
with self.login_user_context(superuser):
|
||||
data['redirect'] = '/hello/'
|
||||
# Absolute paths should continue to work
|
||||
response = self.client.post(endpoint, data)
|
||||
self.assertRedirects(response, redirect_to)
|
||||
|
||||
with self.login_user_context(superuser):
|
||||
data['redirect'] = '../hello/'
|
||||
# Relative paths should continue to work
|
||||
response = self.client.post(endpoint, data)
|
||||
self.assertRedirects(response, redirect_to)
|
||||
|
||||
with self.login_user_context(superuser):
|
||||
data['redirect'] = 'javascript:alert(1)'
|
||||
# Asserts users can't insert javascript call
|
||||
response = self.client.post(endpoint, data)
|
||||
validation_error = '<ul class="errorlist"><li>Enter a valid URL.</li></ul>'
|
||||
self.assertContains(response, validation_error, html=True)
|
||||
|
||||
with self.login_user_context(superuser):
|
||||
data['redirect'] = '<script>alert("test")</script>'
|
||||
# Asserts users can't insert javascript call
|
||||
response = self.client.post(endpoint, data)
|
||||
validation_error = '<ul class="errorlist"><li>Enter a valid URL.</li></ul>'
|
||||
self.assertContains(response, validation_error, html=True)
|
||||
|
||||
def test_moderator_edit_page_redirect(self):
|
||||
"""
|
||||
Test that a page can be edited multiple times with moderator
|
||||
|
||||
@@ -384,6 +384,17 @@ class ToolbarTests(ToolbarTestBase):
|
||||
toolbar = CMSToolbar(request)
|
||||
self.assertTrue(toolbar.show_toolbar)
|
||||
|
||||
def test_toolbar_login_redirect_validation(self):
|
||||
user = self._create_user('toolbar', True, True)
|
||||
username = getattr(user, user.USERNAME_FIELD)
|
||||
page = create_page("toolbar-page", "nav_playground.html", "en", published=True)
|
||||
endpoint = '{}?{}&cms-toolbar-login&next=https://notyourdomain.com'.format(
|
||||
page.get_absolute_url(),
|
||||
get_cms_setting('CMS_TOOLBAR_URL__EDIT_ON')
|
||||
)
|
||||
response = self.client.post(endpoint, {'username': username, 'password': username})
|
||||
self.assertRedirects(response, page.get_absolute_url(), fetch_redirect_response=False)
|
||||
|
||||
def test_show_toolbar_login_anonymous(self):
|
||||
create_page("toolbar-page", "nav_playground.html", "en", published=True)
|
||||
response = self.client.get('/en/?%s' % get_cms_setting('CMS_TOOLBAR_URL__EDIT_ON'))
|
||||
|
||||
+14
-7
@@ -18,6 +18,7 @@ from django.core.urlresolvers import resolve, Resolver404
|
||||
from django.http import HttpResponseRedirect, HttpResponse
|
||||
from django.middleware.csrf import get_token
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.http import is_safe_url
|
||||
|
||||
|
||||
class CMSToolbarLoginForm(AuthenticationForm):
|
||||
@@ -367,19 +368,25 @@ class CMSToolbar(ToolbarAPIMixin):
|
||||
logout(self.request)
|
||||
return HttpResponseRedirect(self.request.path_info)
|
||||
|
||||
def _get_login_redirect_to(self):
|
||||
redirect_to = self.request.GET.get(REDIRECT_FIELD_NAME)
|
||||
|
||||
if is_safe_url(url=redirect_to, host=self.request.get_host()):
|
||||
return redirect_to
|
||||
return
|
||||
|
||||
def _request_hook_post(self):
|
||||
# login hook
|
||||
if 'cms-toolbar-login' in self.request.GET:
|
||||
redirect_to = self._get_login_redirect_to()
|
||||
self.login_form = CMSToolbarLoginForm(request=self.request, data=self.request.POST)
|
||||
|
||||
if self.login_form.is_valid():
|
||||
login(self.request, self.login_form.user_cache)
|
||||
if REDIRECT_FIELD_NAME in self.request.GET:
|
||||
return HttpResponseRedirect(self.request.GET[REDIRECT_FIELD_NAME])
|
||||
else:
|
||||
return HttpResponseRedirect(self.request.path_info)
|
||||
else:
|
||||
if REDIRECT_FIELD_NAME in self.request.GET:
|
||||
return HttpResponseRedirect(self.request.GET[REDIRECT_FIELD_NAME]+"?cms-toolbar-login-error=1")
|
||||
redirect_to = redirect_to or self.request.path_info
|
||||
return HttpResponseRedirect(redirect_to)
|
||||
elif redirect_to:
|
||||
return HttpResponseRedirect(redirect_to + "?cms-toolbar-login-error=1")
|
||||
|
||||
def _call_toolbar(self, func_name):
|
||||
with force_language(self.toolbar_language):
|
||||
|
||||
@@ -14,6 +14,14 @@ from cms.utils.conf import get_cms_setting
|
||||
# checks validity of absolute / relative url
|
||||
any_path_re = re.compile('^/?[a-zA-Z0-9_.-]+(/[a-zA-Z0-9_.-]+)*/?$')
|
||||
|
||||
# checks validity of relative url
|
||||
# matches the following:
|
||||
# /test
|
||||
# /test/
|
||||
# ./test/
|
||||
# ../test/
|
||||
relative_url_regex = re.compile('^[^/<>]+/[^/<>].*$|^/[^/<>].*$', re.IGNORECASE)
|
||||
|
||||
|
||||
def levelize_path(path):
|
||||
"""Splits given path to list of paths removing latest level in each step.
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
.. _upgrade-to-3.4.3:
|
||||
|
||||
###################
|
||||
3.4.3 release notes
|
||||
###################
|
||||
|
||||
*******************
|
||||
What's new in 3.4.3
|
||||
*******************
|
||||
|
||||
Security Fixes
|
||||
==============
|
||||
|
||||
* Fixed a security vulnerability in the page redirect field which allowed users
|
||||
to insert JavaScript code.
|
||||
* Fixed a security vulnerability where the ``next`` parameter for the toolbar login
|
||||
was not sanitised and could point to another domain.
|
||||
|
||||
|
||||
Thanks
|
||||
======
|
||||
|
||||
Thanks to Mark Walker and Anthony Steinhauser for reporting the security issues.
|
||||
@@ -13,6 +13,7 @@ makes changes to your database.
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
3.4.3
|
||||
3.4.2
|
||||
3.4.1
|
||||
3.4
|
||||
|
||||
Reference in New Issue
Block a user