Skip to content

Commit 186eb70

Browse files
committed
Fixed django-cms#5938 -- Ensure page redirect url is a valid url
1 parent 20c5bf0 commit 186eb70

File tree

5 files changed

+68
-1
lines changed

5 files changed

+68
-1
lines changed

CHANGELOG.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919

2020
=== 3.4.3 (2017-04-24) ===
2121

22-
22+
* Fixed a security vulnerability in the page redirect field which allowed users
23+
to insert javascript code.
2324

2425

2526
=== 3.4.2 (2017-01-23) ===

cms/forms/fields.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from django.utils.translation import ugettext_lazy as _
66

77
from cms.forms.utils import get_site_choices, get_page_choices
8+
from cms.forms.validators import validate_url
89
from cms.forms.widgets import PageSelectWidget, PageSmartLinkWidget
910
from cms.models.pagemodel import Page
1011

@@ -75,8 +76,10 @@ def has_changed(self, initial, data):
7576
def _has_changed(self, initial, data):
7677
return self.has_changed(initial, data)
7778

79+
7880
class PageSmartLinkField(forms.CharField):
7981
widget = PageSmartLinkWidget
82+
default_validators = [validate_url]
8083

8184
def __init__(self, max_length=None, min_length=None, placeholder_text=None,
8285
ajax_view=None, *args, **kwargs):
@@ -89,3 +92,7 @@ def widget_attrs(self, widget):
8992
attrs = super(PageSmartLinkField, self).widget_attrs(widget)
9093
attrs.update({'placeholder_text': self.placeholder_text})
9194
return attrs
95+
96+
def clean(self, value):
97+
value = self.to_python(value).strip()
98+
return super(PageSmartLinkField, self).clean(value)

cms/forms/validators.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from django.core.exceptions import ValidationError
2+
from django.core.validators import RegexValidator, URLValidator
3+
4+
from cms.utils.urlutils import relative_url_regex
5+
6+
7+
def validate_url(value):
8+
try:
9+
# Validate relative urls first
10+
RegexValidator(regex=relative_url_regex)(value)
11+
except ValidationError:
12+
# Fallback to absolute urls
13+
URLValidator()(value)

cms/tests/test_page_admin.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,44 @@ def test_edit_page_sets_publisher_dirty(self):
344344
self.client.post(endpoint, page_data)
345345
self.assertTrue(page.reload().is_dirty('en'), change_message.format(field))
346346

347+
def test_page_redirect_field_validation(self):
348+
superuser = self.get_superuser()
349+
data = self.get_new_page_data()
350+
351+
with self.login_user_context(superuser):
352+
self.client.post(URL_CMS_PAGE_ADD, data)
353+
354+
page = Page.objects.get(title_set__slug=data['slug'], publisher_is_draft=True)
355+
356+
endpoint = URL_CMS_PAGE_ADVANCED_CHANGE % page.pk
357+
redirect_to = URL_CMS_PAGE
358+
359+
with self.login_user_context(superuser):
360+
data['redirect'] = '/hello/'
361+
# Absolute paths should continue to work
362+
response = self.client.post(endpoint, data)
363+
self.assertRedirects(response, redirect_to)
364+
365+
with self.login_user_context(superuser):
366+
data['redirect'] = '../hello/'
367+
# Relative paths should continue to work
368+
response = self.client.post(endpoint, data)
369+
self.assertRedirects(response, redirect_to)
370+
371+
with self.login_user_context(superuser):
372+
data['redirect'] = 'javascript:alert(1)'
373+
# Asserts users can't insert javascript call
374+
response = self.client.post(endpoint, data)
375+
validation_error = '<ul class="errorlist"><li>Enter a valid URL.</li></ul>'
376+
self.assertContains(response, validation_error, html=True)
377+
378+
with self.login_user_context(superuser):
379+
data['redirect'] = '<script>alert("test")</script>'
380+
# Asserts users can't insert javascript call
381+
response = self.client.post(endpoint, data)
382+
validation_error = '<ul class="errorlist"><li>Enter a valid URL.</li></ul>'
383+
self.assertContains(response, validation_error, html=True)
384+
347385
def test_moderator_edit_page_redirect(self):
348386
"""
349387
Test that a page can be edited multiple times with moderator

cms/utils/urlutils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@
1414
# checks validity of absolute / relative url
1515
any_path_re = re.compile('^/?[a-zA-Z0-9_.-]+(/[a-zA-Z0-9_.-]+)*/?$')
1616

17+
# checks validity of relative url
18+
# matches the following:
19+
# /test
20+
# /test/
21+
# ./test/
22+
# ../test/
23+
relative_url_regex = re.compile('^[^/<>]+/[^/<>].*$|^/[^/<>].*$', re.IGNORECASE)
24+
1725

1826
def levelize_path(path):
1927
"""Splits given path to list of paths removing latest level in each step.

0 commit comments

Comments
 (0)