mirror of https://github.com/django/django.git
Fixed CVE-2019-12308 -- Made AdminURLFieldWidget validate URL before rendering clickable link.
This commit is contained in:
parent
98c0fe19ee
commit
deeba6d920
|
@ -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 %}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import json
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
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
|
||||||
|
@ -330,14 +331,21 @@ 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):
|
||||||
super().__init__(attrs={'class': 'vURLField', **(attrs or {})})
|
super().__init__(attrs={'class': 'vURLField', **(attrs or {})})
|
||||||
|
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().get_context(name, value, attrs)
|
context = super().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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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`.
|
||||||
|
|
|
@ -5,3 +5,17 @@ Django 2.1.9 release notes
|
||||||
*June 3, 2019*
|
*June 3, 2019*
|
||||||
|
|
||||||
Django 2.1.9 fixes security issues in 2.1.8.
|
Django 2.1.9 fixes security issues in 2.1.8.
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
|
@ -6,6 +6,20 @@ Django 2.2.2 release notes
|
||||||
|
|
||||||
Django 2.2.2 fixes security issues and several bugs in 2.2.1.
|
Django 2.2.2 fixes security issues and several bugs in 2.2.1.
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
Bugfixes
|
Bugfixes
|
||||||
========
|
========
|
||||||
|
|
||||||
|
|
|
@ -333,6 +333,13 @@ 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!")']:
|
||||||
|
with self.subTest(url=invalid):
|
||||||
|
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(
|
||||||
|
@ -366,31 +373,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/<sometag>some text</sometag>',
|
'http://example.com/<sometag>some-text</sometag>',
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
VALUE_RE.search(output).groups()[0],
|
VALUE_RE.search(output).groups()[0],
|
||||||
'http://example.com/<sometag>some text</sometag>',
|
'http://example.com/<sometag>some-text</sometag>',
|
||||||
)
|
)
|
||||||
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/<sometag>some text</sometag>',
|
'http://example-äüö.com/<sometag>some-text</sometag>',
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
VALUE_RE.search(output).groups()[0],
|
VALUE_RE.search(output).groups()[0],
|
||||||
'http://example-äüö.com/<sometag>some text</sometag>',
|
'http://example-äüö.com/<sometag>some-text</sometag>',
|
||||||
)
|
)
|
||||||
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(
|
||||||
|
|
Loading…
Reference in New Issue