summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Dufresne <jon.dufresne@gmail.com>2020-05-26 09:51:02 +0200
committerCarlton Gibson <carlton.gibson@noumenal.es>2020-06-03 09:32:35 +0200
commit1f2dd37f6fcefdd10ed44cb233b2e62b520afb38 (patch)
tree5bfe03d368a683e3b08504de32a14d3d309f7f8b
parent256d29710193f7a2f1e92abe96c94d036f73edc6 (diff)
[3.0.x] Fixed CVE-2020-13596 -- Fixed potential XSS in admin ForeignKeyRawIdWidget.
-rw-r--r--django/contrib/admin/widgets.py6
-rw-r--r--docs/releases/2.2.13.txt7
-rw-r--r--docs/releases/3.0.7.txt7
-rw-r--r--tests/admin_widgets/models.py8
-rw-r--r--tests/admin_widgets/tests.py11
5 files changed, 36 insertions, 3 deletions
diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py
index 7db57f4098..816f848ff0 100644
--- a/django/contrib/admin/widgets.py
+++ b/django/contrib/admin/widgets.py
@@ -12,7 +12,7 @@ from django.db.models.deletion import CASCADE
from django.urls import reverse
from django.urls.exceptions import NoReverseMatch
from django.utils.html import smart_urlquote
-from django.utils.safestring import mark_safe
+from django.utils.http import urlencode
from django.utils.text import Truncator
from django.utils.translation import get_language, gettext as _
@@ -150,8 +150,8 @@ class ForeignKeyRawIdWidget(forms.TextInput):
params = self.url_parameters()
if params:
- related_url += '?' + '&amp;'.join('%s=%s' % (k, v) for k, v in params.items())
- context['related_url'] = mark_safe(related_url)
+ related_url += '?' + urlencode(params)
+ context['related_url'] = related_url
context['link_title'] = _('Lookup')
# The JavaScript code looks for this class.
context['widget']['attrs'].setdefault('class', 'vForeignKeyRawIdAdminField')
diff --git a/docs/releases/2.2.13.txt b/docs/releases/2.2.13.txt
index 2e149a1f18..ee381fdcce 100644
--- a/docs/releases/2.2.13.txt
+++ b/docs/releases/2.2.13.txt
@@ -6,6 +6,13 @@ Django 2.2.13 release notes
Django 2.2.13 fixes two security issues and a regression in 2.2.12.
+CVE-2020-13596: Possible XSS via admin ``ForeignKeyRawIdWidget``
+================================================================
+
+Query parameters for the admin ``ForeignKeyRawIdWidget`` were not properly URL
+encoded, posing an XSS attack vector. ``ForeignKeyRawIdWidget`` now
+ensures query parameters are correctly URL encoded.
+
Bugfixes
========
diff --git a/docs/releases/3.0.7.txt b/docs/releases/3.0.7.txt
index f1775b3471..51ac0d7edd 100644
--- a/docs/releases/3.0.7.txt
+++ b/docs/releases/3.0.7.txt
@@ -6,6 +6,13 @@ Django 3.0.7 release notes
Django 3.0.7 fixes two security issues and several bugs in 3.0.6.
+CVE-2020-13596: Possible XSS via admin ``ForeignKeyRawIdWidget``
+================================================================
+
+Query parameters for the admin ``ForeignKeyRawIdWidget`` were not properly URL
+encoded, posing an XSS attack vector. ``ForeignKeyRawIdWidget`` now
+ensures query parameters are correctly URL encoded.
+
Bugfixes
========
diff --git a/tests/admin_widgets/models.py b/tests/admin_widgets/models.py
index b5025fdfd7..88bf2b8fca 100644
--- a/tests/admin_widgets/models.py
+++ b/tests/admin_widgets/models.py
@@ -27,6 +27,14 @@ class Band(models.Model):
return self.name
+class UnsafeLimitChoicesTo(models.Model):
+ band = models.ForeignKey(
+ Band,
+ models.CASCADE,
+ limit_choices_to={'name': '"&><escapeme'},
+ )
+
+
class Album(models.Model):
band = models.ForeignKey(Band, models.CASCADE)
featuring = models.ManyToManyField(Band, related_name='featured')
diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py
index 7aa597a87d..7d3d71181e 100644
--- a/tests/admin_widgets/tests.py
+++ b/tests/admin_widgets/tests.py
@@ -22,6 +22,7 @@ from django.utils import translation
from .models import (
Advisor, Album, Band, Bee, Car, Company, Event, Honeycomb, Individual,
Inventory, Member, MyFileField, Profile, School, Student,
+ UnsafeLimitChoicesTo,
)
from .widgetadmin import site as widget_admin_site
@@ -586,6 +587,16 @@ class ForeignKeyRawIdWidgetTest(TestCase):
'Hidden</a></strong>' % {'pk': hidden.pk}
)
+ def test_render_unsafe_limit_choices_to(self):
+ rel = UnsafeLimitChoicesTo._meta.get_field('band').remote_field
+ w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site)
+ self.assertHTMLEqual(
+ w.render('test', None),
+ '<input type="text" name="test" class="vForeignKeyRawIdAdminField">\n'
+ '<a href="/admin_widgets/band/?name=%22%26%3E%3Cescapeme&amp;_to_field=id" '
+ 'class="related-lookup" id="lookup_id_test" title="Lookup"></a>'
+ )
+
@override_settings(ROOT_URLCONF='admin_widgets.urls')
class ManyToManyRawIdWidgetTest(TestCase):