티스토리 수익 글 보기

티스토리 수익 글 보기

[2.1.x] Fixed CVE-2019-12308 — Made AdminURLFieldWidget validate URL… · django/django@09186a1 · GitHub
Skip to content

Commit 09186a1

Browse files
committed
[2.1.x] Fixed CVE-2019-12308 — Made AdminURLFieldWidget validate URL before rendering clickable link.
Backport of deeba6d from master.
1 parent f6e2b55 commit 09186a1

File tree

5 files changed

+54
11
lines changed

5 files changed

+54
11
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +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 %}
1+
{% 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 %}

django/contrib/admin/widgets.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from django import forms
88
from django.conf import settings
99
from django.core.exceptions import ValidationError
10+
from django.core.validators import URLValidator
1011
from django.db.models.deletion import CASCADE
1112
from django.urls import reverse
1213
from django.urls.exceptions import NoReverseMatch
@@ -335,14 +336,21 @@ def __init__(self, attrs=None):
335336
class AdminURLFieldWidget(forms.URLInput):
336337
template_name = 'admin/widgets/url.html'
337338

338-
def __init__(self, attrs=None):
339+
def __init__(self, attrs=None, validator_class=URLValidator):
339340
super().__init__(attrs={'class': 'vURLField', **(attrs or {})})
341+
self.validator = validator_class()
340342

341343
def get_context(self, name, value, attrs):
344+
try:
345+
self.validator(value if value else '')
346+
url_valid = True
347+
except ValidationError:
348+
url_valid = False
342349
context = super().get_context(name, value, attrs)
343350
context['current_label'] = _('Currently:')
344351
context['change_label'] = _('Change:')
345352
context['widget']['href'] = smart_urlquote(context['widget']['value']) if value else ''
353+
context['url_valid'] = url_valid
346354
return context
347355

348356

docs/releases/1.11.21.txt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,18 @@ Django 1.11.21 release notes
44

55
*June 3, 2019*
66

7-
Django 1.11.21 fixes security issues in 1.11.20.
7+
Django 1.11.21 fixes a security issue in 1.11.20.
8+
9+
CVE-2019-12308: AdminURLFieldWidget XSS
10+
---------------------------------------
11+
12+
The clickable "Current URL" link generated by ``AdminURLFieldWidget`` displayed
13+
the provided value without validating it as a safe URL. Thus, an unvalidated
14+
value stored in the database, or a value provided as a URL query parameter
15+
payload, could result in an clickable JavaScript link.
16+
17+
``AdminURLFieldWidget`` now validates the provided value using
18+
:class:`~django.core.validators.URLValidator` before displaying the clickable
19+
link. You may customise the validator by passing a ``validator_class`` kwarg to
20+
``AdminURLFieldWidget.__init__()``, e.g. when using
21+
:attr:`~django.contrib.admin.ModelAdmin.formfield_overrides`.

docs/releases/2.1.9.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,17 @@ Django 2.1.9 release notes
55
*June 3, 2019*
66

77
Django 2.1.9 fixes security issues in 2.1.8.
8+
9+
CVE-2019-12308: AdminURLFieldWidget XSS
10+
---------------------------------------
11+
12+
The clickable "Current URL" link generated by ``AdminURLFieldWidget`` displayed
13+
the provided value without validating it as a safe URL. Thus, an unvalidated
14+
value stored in the database, or a value provided as a URL query parameter
15+
payload, could result in an clickable JavaScript link.
16+
17+
``AdminURLFieldWidget`` now validates the provided value using
18+
:class:`~django.core.validators.URLValidator` before displaying the clickable
19+
link. You may customise the validator by passing a ``validator_class`` kwarg to
20+
``AdminURLFieldWidget.__init__()``, e.g. when using
21+
:attr:`~django.contrib.admin.ModelAdmin.formfield_overrides`.

tests/admin_widgets/tests.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,13 @@ def test_localization(self):
333333

334334

335335
class AdminURLWidgetTest(SimpleTestCase):
336+
def test_get_context_validates_url(self):
337+
w = widgets.AdminURLFieldWidget()
338+
for invalid in ['', '/not/a/full/url/', 'javascript:alert("Danger XSS!")']:
339+
with self.subTest(url=invalid):
340+
self.assertFalse(w.get_context('name', invalid, {})['url_valid'])
341+
self.assertTrue(w.get_context('name', 'http://example.com', {})['url_valid'])
342+
336343
def test_render(self):
337344
w = widgets.AdminURLFieldWidget()
338345
self.assertHTMLEqual(
@@ -366,31 +373,31 @@ def test_render_quoting(self):
366373
VALUE_RE = re.compile('value="([^"]+)"')
367374
TEXT_RE = re.compile('<a[^>]+>([^>]+)</a>')
368375
w = widgets.AdminURLFieldWidget()
369-
output = w.render('test', 'http://example.com/<sometag>some text</sometag>')
376+
output = w.render('test', 'http://example.com/<sometag>some-text</sometag>')
370377
self.assertEqual(
371378
HREF_RE.search(output).groups()[0],
372-
'http://example.com/%3Csometag%3Esome%20text%3C/sometag%3E',
379+
'http://example.com/%3Csometag%3Esome-text%3C/sometag%3E',
373380
)
374381
self.assertEqual(
375382
TEXT_RE.search(output).groups()[0],
376-
'http://example.com/&lt;sometag&gt;some text&lt;/sometag&gt;',
383+
'http://example.com/&lt;sometag&gt;some-text&lt;/sometag&gt;',
377384
)
378385
self.assertEqual(
379386
VALUE_RE.search(output).groups()[0],
380-
'http://example.com/&lt;sometag&gt;some text&lt;/sometag&gt;',
387+
'http://example.com/&lt;sometag&gt;some-text&lt;/sometag&gt;',
381388
)
382-
output = w.render('test', 'http://example-äüö.com/<sometag>some text</sometag>')
389+
output = w.render('test', 'http://example-äüö.com/<sometag>some-text</sometag>')
383390
self.assertEqual(
384391
HREF_RE.search(output).groups()[0],
385-
'http://xn--example--7za4pnc.com/%3Csometag%3Esome%20text%3C/sometag%3E',
392+
'http://xn--example--7za4pnc.com/%3Csometag%3Esome-text%3C/sometag%3E',
386393
)
387394
self.assertEqual(
388395
TEXT_RE.search(output).groups()[0],
389-
'http://example-äüö.com/&lt;sometag&gt;some text&lt;/sometag&gt;',
396+
'http://example-äüö.com/&lt;sometag&gt;some-text&lt;/sometag&gt;',
390397
)
391398
self.assertEqual(
392399
VALUE_RE.search(output).groups()[0],
393-
'http://example-äüö.com/&lt;sometag&gt;some text&lt;/sometag&gt;',
400+
'http://example-äüö.com/&lt;sometag&gt;some-text&lt;/sometag&gt;',
394401
)
395402
output = w.render('test', 'http://www.example.com/%C3%A4"><script>alert("XSS!")</script>"')
396403
self.assertEqual(

0 commit comments

Comments
 (0)