[1.11.x] Fixed CVE-2019-12308 -- Made AdminURLFieldWidget validate URL before rendering clickable link.

Backport of deeba6d92006999fee9adfbd8be79bf0a59e8008 from master.
This commit is contained in:
Carlton Gibson 2019-05-27 12:10:02 +02:00
parent 4b3716e654
commit c238701859
4 changed files with 39 additions and 11 deletions

View File

@ -1 +1 @@
{% if widget.value %}<p class="url">{{ current_label }} <a href="{{ widget.href }}">{{ widget.value }}</a><br />{{ change_label }} {% endif %}{% include "django/forms/widgets/input.html" %}{% if widget.value %}</p>{% endif %} {% if url_valid %}<p class="url">{{ current_label }} <a href="{{ widget.href }}">{{ widget.value }}</a><br />{{ change_label }} {% endif %}{% include "django/forms/widgets/input.html" %}{% if url_valid %}</p>{% endif %}

View File

@ -7,6 +7,7 @@ import copy
from django import forms from django import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.validators import URLValidator
from django.db.models.deletion import CASCADE from django.db.models.deletion import CASCADE
from django.urls import reverse from django.urls import reverse
from django.urls.exceptions import NoReverseMatch from django.urls.exceptions import NoReverseMatch
@ -339,17 +340,24 @@ class AdminEmailInputWidget(forms.EmailInput):
class AdminURLFieldWidget(forms.URLInput): class AdminURLFieldWidget(forms.URLInput):
template_name = 'admin/widgets/url.html' template_name = 'admin/widgets/url.html'
def __init__(self, attrs=None): def __init__(self, attrs=None, validator_class=URLValidator):
final_attrs = {'class': 'vURLField'} final_attrs = {'class': 'vURLField'}
if attrs is not None: if attrs is not None:
final_attrs.update(attrs) final_attrs.update(attrs)
super(AdminURLFieldWidget, self).__init__(attrs=final_attrs) super(AdminURLFieldWidget, self).__init__(attrs=final_attrs)
self.validator = validator_class()
def get_context(self, name, value, attrs): def get_context(self, name, value, attrs):
try:
self.validator(value if value else '')
url_valid = True
except ValidationError:
url_valid = False
context = super(AdminURLFieldWidget, self).get_context(name, value, attrs) context = super(AdminURLFieldWidget, self).get_context(name, value, attrs)
context['current_label'] = _('Currently:') context['current_label'] = _('Currently:')
context['change_label'] = _('Change:') context['change_label'] = _('Change:')
context['widget']['href'] = smart_urlquote(context['widget']['value']) if value else '' context['widget']['href'] = smart_urlquote(context['widget']['value']) if value else ''
context['url_valid'] = url_valid
return context return context

View File

@ -4,4 +4,18 @@ Django 1.11.21 release notes
*June 3, 2019* *June 3, 2019*
Django 1.11.21 fixes security issues in 1.11.20. Django 1.11.21 fixes a security issue in 1.11.20.
CVE-2019-12308: AdminURLFieldWidget XSS
---------------------------------------
The clickable "Current URL" link generated by ``AdminURLFieldWidget`` displayed
the provided value without validating it as a safe URL. Thus, an unvalidated
value stored in the database, or a value provided as a URL query parameter
payload, could result in an clickable JavaScript link.
``AdminURLFieldWidget`` now validates the provided value using
:class:`~django.core.validators.URLValidator` before displaying the clickable
link. You may customise the validator by passing a ``validator_class`` kwarg to
``AdminURLFieldWidget.__init__()``, e.g. when using
:attr:`~django.contrib.admin.ModelAdmin.formfield_overrides`.

View File

@ -336,6 +336,12 @@ class AdminSplitDateTimeWidgetTest(SimpleTestCase):
class AdminURLWidgetTest(SimpleTestCase): class AdminURLWidgetTest(SimpleTestCase):
def test_get_context_validates_url(self):
w = widgets.AdminURLFieldWidget()
for invalid in ['', '/not/a/full/url/', 'javascript:alert("Danger XSS!")']:
self.assertFalse(w.get_context('name', invalid, {})['url_valid'])
self.assertTrue(w.get_context('name', 'http://example.com', {})['url_valid'])
def test_render(self): def test_render(self):
w = widgets.AdminURLFieldWidget() w = widgets.AdminURLFieldWidget()
self.assertHTMLEqual( self.assertHTMLEqual(
@ -369,31 +375,31 @@ class AdminURLWidgetTest(SimpleTestCase):
VALUE_RE = re.compile('value="([^"]+)"') VALUE_RE = re.compile('value="([^"]+)"')
TEXT_RE = re.compile('<a[^>]+>([^>]+)</a>') TEXT_RE = re.compile('<a[^>]+>([^>]+)</a>')
w = widgets.AdminURLFieldWidget() w = widgets.AdminURLFieldWidget()
output = w.render('test', 'http://example.com/<sometag>some text</sometag>') output = w.render('test', 'http://example.com/<sometag>some-text</sometag>')
self.assertEqual( self.assertEqual(
HREF_RE.search(output).groups()[0], HREF_RE.search(output).groups()[0],
'http://example.com/%3Csometag%3Esome%20text%3C/sometag%3E', 'http://example.com/%3Csometag%3Esome-text%3C/sometag%3E',
) )
self.assertEqual( self.assertEqual(
TEXT_RE.search(output).groups()[0], TEXT_RE.search(output).groups()[0],
'http://example.com/&lt;sometag&gt;some text&lt;/sometag&gt;', 'http://example.com/&lt;sometag&gt;some-text&lt;/sometag&gt;',
) )
self.assertEqual( self.assertEqual(
VALUE_RE.search(output).groups()[0], VALUE_RE.search(output).groups()[0],
'http://example.com/&lt;sometag&gt;some text&lt;/sometag&gt;', 'http://example.com/&lt;sometag&gt;some-text&lt;/sometag&gt;',
) )
output = w.render('test', 'http://example-äüö.com/<sometag>some text</sometag>') output = w.render('test', 'http://example-äüö.com/<sometag>some-text</sometag>')
self.assertEqual( self.assertEqual(
HREF_RE.search(output).groups()[0], HREF_RE.search(output).groups()[0],
'http://xn--example--7za4pnc.com/%3Csometag%3Esome%20text%3C/sometag%3E', 'http://xn--example--7za4pnc.com/%3Csometag%3Esome-text%3C/sometag%3E',
) )
self.assertEqual( self.assertEqual(
TEXT_RE.search(output).groups()[0], TEXT_RE.search(output).groups()[0],
'http://example-äüö.com/&lt;sometag&gt;some text&lt;/sometag&gt;', 'http://example-äüö.com/&lt;sometag&gt;some-text&lt;/sometag&gt;',
) )
self.assertEqual( self.assertEqual(
VALUE_RE.search(output).groups()[0], VALUE_RE.search(output).groups()[0],
'http://example-äüö.com/&lt;sometag&gt;some text&lt;/sometag&gt;', 'http://example-äüö.com/&lt;sometag&gt;some-text&lt;/sometag&gt;',
) )
output = w.render('test', 'http://www.example.com/%C3%A4"><script>alert("XSS!")</script>"') output = w.render('test', 'http://www.example.com/%C3%A4"><script>alert("XSS!")</script>"')
self.assertEqual( self.assertEqual(