티스토리 수익 글 보기

티스토리 수익 글 보기

[5.2.x] Fixed CVE-2026-15920 — Made display_for_field() validate URL… · django/django@b9adb81 · GitHub
Skip to content

Commit b9adb81

Browse files
committed
[5.2.x] Fixed CVE-2026-15920 — Made display_for_field() validate URLs before rendering admin links.
The admin renders URLField values as clickable links on changelists and read-only change forms. The link was built without validating the URL, so a potentially dangerous stored value could be rendered as a link that runs script in a staff member’s authenticated session when clicked. The admin renders URLField values as clickable links on changelists and read-only change forms. The link was built without validating the URL, so a stored value using a potentially dangerous value was rendered as a link, which could lead to cross-site scripting in an authenticated admin session. Refs CVE-2019-12308, #36032. Thanks to Egor Saltykov for the report, and Sarah Boyce for reviews. Backport of 47511a2 from main.
1 parent ba80833 commit b9adb81

3 files changed

Lines changed: 38 additions & 2 deletions

File tree

django/contrib/admin/utils.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
from functools import reduce
66
from operator import or_
77

8-
from django.core.exceptions import FieldDoesNotExist
9-
from django.core.validators import EMPTY_VALUES
8+
from django.core.exceptions import FieldDoesNotExist, ValidationError
9+
from django.core.validators import EMPTY_VALUES, URLValidator
1010
from django.db import models, router
1111
from django.db.models.constants import LOOKUP_SEP
1212
from django.db.models.deletion import Collector
@@ -455,6 +455,14 @@ def display_for_field(value, field, empty_value_display, avoid_link=False):
455455
elif isinstance(field, models.FileField) and value and not avoid_link:
456456
return format_html('<a href="{}">{}</a>', value.url, value)
457457
elif isinstance(field, models.URLField) and value and not avoid_link:
458+
# Only render a clickable link for URLs with a safe scheme, so that a
459+
# potentially dangerous stored value is shown as plain text rather than
460+
# an executable link. The check is deliberately independent of the
461+
# field's own validators, which may permit such schemes.
462+
try:
463+
URLValidator()(value)
464+
except ValidationError:
465+
return display_for_value(value, empty_value_display)
458466
return format_html('<a href="{}">{}</a>', value, value)
459467
elif isinstance(field, models.JSONField) and value:
460468
try:

docs/releases/5.2.17.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,18 @@ affected.
7979

8080
This issue has severity "moderate" according to the :ref:`Django security
8181
policy <severity-levels>`.
82+
83+
CVE-2026-15920: Potential cross-site scripting via ``URLField`` values in the admin
84+
===================================================================================
85+
86+
The admin renders :class:`~django.db.models.URLField` values as clickable links
87+
on changelist views and read-only fields. The link was generated without
88+
validating the value as a safe URL, so a stored value using a potentially
89+
dangerous scheme was rendered as a link.
90+
91+
``URLField`` values shown via ``display_for_field`` are now validated using
92+
:class:`~django.core.validators.URLValidator` before a link is rendered, and
93+
displayed as plain text if validation is failed.
94+
95+
This issue has severity "moderate" according to the :ref:`Django security
96+
policy <severity-levels>`.

tests/admin_utils/tests.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,19 @@ def test_url_display_for_field(self):
205205
expected = '<a href="http://example.com">http://example.com</a>'
206206
self.assertHTMLEqual(display_value, expected)
207207

208+
def test_url_display_for_field_invalid_url(self):
209+
# An invalid URL, such as one with an unsafe scheme, is rendered as
210+
# plain text instead of a clickable link.
211+
model_field = models.URLField()
212+
for value in [
213+
"javascript:alert(1)",
214+
"data:text/html,<script>alert(1)</script>",
215+
]:
216+
with self.subTest(value=value):
217+
display_value = display_for_field(value, model_field, self.empty_value)
218+
self.assertNotIn("<a", display_value)
219+
self.assertEqual(display_value, value)
220+
208221
def test_number_formats_display_for_field(self):
209222
display_value = display_for_field(
210223
12345.6789, models.FloatField(), self.empty_value

0 commit comments

Comments
 (0)