VYPR
Moderate severityNVD Advisory· Published Jun 3, 2019· Updated Aug 4, 2024

CVE-2019-12308

CVE-2019-12308

Description

An issue was discovered in Django 1.11 before 1.11.21, 2.1 before 2.1.9, and 2.2 before 2.2.2. The clickable Current URL value displayed by the AdminURLFieldWidget displays 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.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Django AdminURLFieldWidget lacks URL validation, enabling stored or reflected XSS via clickable current URL link.

Vulnerability

The Django AdminURLFieldWidget generates a clickable "Current URL" link in the admin interface but fails to validate the provided value as a safe URL [2]. This allows an attacker to inject arbitrary JavaScript through an unvalidated value stored in the database or supplied as a URL query parameter [4].

Exploitation

An attacker who can control a URL field in the database (e.g., via a model instance) or craft a query parameter that is rendered by the admin widget can cause the browser to execute malicious script when an admin user clicks the link [4]. No special privileges are required beyond the ability to store or supply the malicious value; however, the attack targets admin users.

Impact

Successful exploitation leads to cross-site scripting (XSS) in the admin interface, potentially enabling session hijacking, data theft, or further actions within the admin context [2].

Mitigation

The vulnerability is fixed in Django versions 1.11.21, 2.1.9, and 2.2.2 [4]. Users are advised to upgrade immediately. No workaround is available.

AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
DjangoPyPI
>= 1.11a1, < 1.11.211.11.21
DjangoPyPI
>= 2.1a1, < 2.1.92.1.9
DjangoPyPI
>= 2.2a1, < 2.2.22.2.2

Affected products

13

Patches

3
c238701859a5

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

https://github.com/django/djangoCarlton GibsonMay 27, 2019via ghsa
4 files changed · +39 11
  • django/contrib/admin/templates/admin/widgets/url.html+1 1 modified
    @@ -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 %}
    
  • django/contrib/admin/widgets.py+9 1 modified
    @@ -7,6 +7,7 @@
     
     from django import forms
     from django.core.exceptions import ValidationError
    +from django.core.validators import URLValidator
     from django.db.models.deletion import CASCADE
     from django.urls import reverse
     from django.urls.exceptions import NoReverseMatch
    @@ -339,17 +340,24 @@ def __init__(self, attrs=None):
     class AdminURLFieldWidget(forms.URLInput):
         template_name = 'admin/widgets/url.html'
     
    -    def __init__(self, attrs=None):
    +    def __init__(self, attrs=None, validator_class=URLValidator):
             final_attrs = {'class': 'vURLField'}
             if attrs is not None:
                 final_attrs.update(attrs)
             super(AdminURLFieldWidget, self).__init__(attrs=final_attrs)
    +        self.validator = validator_class()
     
         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['current_label'] = _('Currently:')
             context['change_label'] = _('Change:')
             context['widget']['href'] = smart_urlquote(context['widget']['value']) if value else ''
    +        context['url_valid'] = url_valid
             return context
     
     
    
  • docs/releases/1.11.21.txt+15 1 modified
    @@ -4,4 +4,18 @@ Django 1.11.21 release notes
     
     *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`.
    
  • tests/admin_widgets/tests.py+14 8 modified
    @@ -336,6 +336,12 @@ def test_localization(self):
     
     
     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):
             w = widgets.AdminURLFieldWidget()
             self.assertHTMLEqual(
    @@ -369,31 +375,31 @@ def test_render_quoting(self):
             VALUE_RE = re.compile('value="([^"]+)"')
             TEXT_RE = re.compile('<a[^>]+>([^>]+)</a>')
             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(
                 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(
                 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(
                 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(
                 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(
                 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(
                 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>"')
             self.assertEqual(
    
09186a13d975

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

https://github.com/django/djangoCarlton GibsonMay 23, 2019via ghsa
5 files changed · +54 11
  • django/contrib/admin/templates/admin/widgets/url.html+1 1 modified
    @@ -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 %}
    
  • django/contrib/admin/widgets.py+9 1 modified
    @@ -7,6 +7,7 @@
     from django import forms
     from django.conf import settings
     from django.core.exceptions import ValidationError
    +from django.core.validators import URLValidator
     from django.db.models.deletion import CASCADE
     from django.urls import reverse
     from django.urls.exceptions import NoReverseMatch
    @@ -335,14 +336,21 @@ def __init__(self, attrs=None):
     class AdminURLFieldWidget(forms.URLInput):
         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 {})})
    +        self.validator = validator_class()
     
         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['current_label'] = _('Currently:')
             context['change_label'] = _('Change:')
             context['widget']['href'] = smart_urlquote(context['widget']['value']) if value else ''
    +        context['url_valid'] = url_valid
             return context
     
     
    
  • docs/releases/1.11.21.txt+15 1 modified
    @@ -4,4 +4,18 @@ Django 1.11.21 release notes
     
     *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`.
    
  • docs/releases/2.1.9.txt+14 0 modified
    @@ -5,3 +5,17 @@ Django 2.1.9 release notes
     *June 3, 2019*
     
     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`.
    
  • tests/admin_widgets/tests.py+15 8 modified
    @@ -333,6 +333,13 @@ def test_localization(self):
     
     
     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):
             w = widgets.AdminURLFieldWidget()
             self.assertHTMLEqual(
    @@ -366,31 +373,31 @@ def test_render_quoting(self):
             VALUE_RE = re.compile('value="([^"]+)"')
             TEXT_RE = re.compile('<a[^>]+>([^>]+)</a>')
             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(
                 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(
                 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(
                 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(
                 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(
                 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(
                 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>"')
             self.assertEqual(
    
afddabf8428d

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

https://github.com/django/djangoCarlton GibsonMay 23, 2019via ghsa
6 files changed · +68 11
  • django/contrib/admin/templates/admin/widgets/url.html+1 1 modified
    @@ -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 %}
    
  • django/contrib/admin/widgets.py+9 1 modified
    @@ -7,6 +7,7 @@
     from django import forms
     from django.conf import settings
     from django.core.exceptions import ValidationError
    +from django.core.validators import URLValidator
     from django.db.models.deletion import CASCADE
     from django.urls import reverse
     from django.urls.exceptions import NoReverseMatch
    @@ -330,14 +331,21 @@ def __init__(self, attrs=None):
     class AdminURLFieldWidget(forms.URLInput):
         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 {})})
    +        self.validator = validator_class()
     
         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['current_label'] = _('Currently:')
             context['change_label'] = _('Change:')
             context['widget']['href'] = smart_urlquote(context['widget']['value']) if value else ''
    +        context['url_valid'] = url_valid
             return context
     
     
    
  • docs/releases/1.11.21.txt+15 1 modified
    @@ -4,4 +4,18 @@ Django 1.11.21 release notes
     
     *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`.
    
  • docs/releases/2.1.9.txt+14 0 modified
    @@ -5,3 +5,17 @@ Django 2.1.9 release notes
     *June 3, 2019*
     
     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`.
    
  • docs/releases/2.2.2.txt+14 0 modified
    @@ -6,6 +6,20 @@ Django 2.2.2 release notes
     
     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
     ========
     
    
  • tests/admin_widgets/tests.py+15 8 modified
    @@ -333,6 +333,13 @@ def test_localization(self):
     
     
     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):
             w = widgets.AdminURLFieldWidget()
             self.assertHTMLEqual(
    @@ -366,31 +373,31 @@ def test_render_quoting(self):
             VALUE_RE = re.compile('value="([^"]+)"')
             TEXT_RE = re.compile('<a[^>]+>([^>]+)</a>')
             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(
                 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(
                 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(
                 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(
                 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(
                 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(
                 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>"')
             self.assertEqual(
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

31

News mentions

0

No linked articles in our index yet.