mirror of
https://github.com/django/django.git
synced 2025-06-02 10:09:12 +00:00
Fixed CVE-2025-27556 -- Mitigated potential DoS in url_has_allowed_host_and_scheme() on Windows.
Thank you sw0rd1ight for the report.
This commit is contained in:
parent
00c68f03b5
commit
39e2297210
@ -6,6 +6,7 @@ from urllib.parse import urlsplit
|
|||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils.deconstruct import deconstructible
|
from django.utils.deconstruct import deconstructible
|
||||||
|
from django.utils.http import MAX_URL_LENGTH
|
||||||
from django.utils.ipv6 import is_valid_ipv6_address
|
from django.utils.ipv6 import is_valid_ipv6_address
|
||||||
from django.utils.regex_helper import _lazy_re_compile
|
from django.utils.regex_helper import _lazy_re_compile
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@ -152,7 +153,7 @@ class URLValidator(RegexValidator):
|
|||||||
message = _("Enter a valid URL.")
|
message = _("Enter a valid URL.")
|
||||||
schemes = ["http", "https", "ftp", "ftps"]
|
schemes = ["http", "https", "ftp", "ftps"]
|
||||||
unsafe_chars = frozenset("\t\r\n")
|
unsafe_chars = frozenset("\t\r\n")
|
||||||
max_length = 2048
|
max_length = MAX_URL_LENGTH
|
||||||
|
|
||||||
def __init__(self, schemes=None, **kwargs):
|
def __init__(self, schemes=None, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
@ -13,7 +13,7 @@ from django.core.exceptions import SuspiciousOperation, ValidationError
|
|||||||
from django.core.validators import EmailValidator
|
from django.core.validators import EmailValidator
|
||||||
from django.utils.deprecation import RemovedInDjango70Warning
|
from django.utils.deprecation import RemovedInDjango70Warning
|
||||||
from django.utils.functional import Promise, cached_property, keep_lazy, keep_lazy_text
|
from django.utils.functional import Promise, cached_property, keep_lazy, keep_lazy_text
|
||||||
from django.utils.http import RFC3986_GENDELIMS, RFC3986_SUBDELIMS
|
from django.utils.http import MAX_URL_LENGTH, RFC3986_GENDELIMS, RFC3986_SUBDELIMS
|
||||||
from django.utils.regex_helper import _lazy_re_compile
|
from django.utils.regex_helper import _lazy_re_compile
|
||||||
from django.utils.safestring import SafeData, SafeString, mark_safe
|
from django.utils.safestring import SafeData, SafeString, mark_safe
|
||||||
from django.utils.text import normalize_newlines
|
from django.utils.text import normalize_newlines
|
||||||
@ -41,7 +41,6 @@ VOID_ELEMENTS = frozenset(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
MAX_URL_LENGTH = 2048
|
|
||||||
MAX_STRIP_TAGS_DEPTH = 50
|
MAX_STRIP_TAGS_DEPTH = 50
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ ASCTIME_DATE = _lazy_re_compile(r"^\w{3} %s %s %s %s$" % (__M, __D2, __T, __Y))
|
|||||||
|
|
||||||
RFC3986_GENDELIMS = ":/?#[]@"
|
RFC3986_GENDELIMS = ":/?#[]@"
|
||||||
RFC3986_SUBDELIMS = "!$&'()*+,;="
|
RFC3986_SUBDELIMS = "!$&'()*+,;="
|
||||||
|
MAX_URL_LENGTH = 2048
|
||||||
|
|
||||||
|
|
||||||
def urlencode(query, doseq=False):
|
def urlencode(query, doseq=False):
|
||||||
@ -274,7 +275,10 @@ def url_has_allowed_host_and_scheme(url, allowed_hosts, require_https=False):
|
|||||||
def _url_has_allowed_host_and_scheme(url, allowed_hosts, require_https=False):
|
def _url_has_allowed_host_and_scheme(url, allowed_hosts, require_https=False):
|
||||||
# Chrome considers any URL with more than two slashes to be absolute, but
|
# Chrome considers any URL with more than two slashes to be absolute, but
|
||||||
# urlsplit is not so flexible. Treat any url with three slashes as unsafe.
|
# urlsplit is not so flexible. Treat any url with three slashes as unsafe.
|
||||||
if url.startswith("///"):
|
if url.startswith("///") or len(url) > MAX_URL_LENGTH:
|
||||||
|
# urlsplit does not perform validation of inputs. Unicode normalization
|
||||||
|
# is very slow on Windows and can be a DoS attack vector.
|
||||||
|
# https://docs.python.org/3/library/urllib.parse.html#url-parsing-security
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
url_info = urlsplit(url)
|
url_info = urlsplit(url)
|
||||||
|
@ -5,3 +5,13 @@ Django 5.0.14 release notes
|
|||||||
*April 2, 2025*
|
*April 2, 2025*
|
||||||
|
|
||||||
Django 5.0.14 fixes a security issue with severity "moderate" in 5.0.13.
|
Django 5.0.14 fixes a security issue with severity "moderate" in 5.0.13.
|
||||||
|
|
||||||
|
CVE-2025-27556: Potential denial-of-service vulnerability in ``LoginView``, ``LogoutView``, and ``set_language()`` on Windows
|
||||||
|
=============================================================================================================================
|
||||||
|
|
||||||
|
Python's :func:`NFKC normalization <python:unicodedata.normalize>` is slow on
|
||||||
|
Windows. As a consequence, :class:`~django.contrib.auth.views.LoginView`,
|
||||||
|
:class:`~django.contrib.auth.views.LogoutView`, and
|
||||||
|
:func:`~django.views.i18n.set_language` were subject to a potential
|
||||||
|
denial-of-service attack via certain inputs with a very large number of Unicode
|
||||||
|
characters.
|
||||||
|
@ -7,6 +7,16 @@ Django 5.1.8 release notes
|
|||||||
Django 5.1.8 fixes a security issue with severity "moderate" and several bugs
|
Django 5.1.8 fixes a security issue with severity "moderate" and several bugs
|
||||||
in 5.1.7.
|
in 5.1.7.
|
||||||
|
|
||||||
|
CVE-2025-27556: Potential denial-of-service vulnerability in ``LoginView``, ``LogoutView``, and ``set_language()`` on Windows
|
||||||
|
=============================================================================================================================
|
||||||
|
|
||||||
|
Python's :func:`NFKC normalization <python:unicodedata.normalize>` is slow on
|
||||||
|
Windows. As a consequence, :class:`~django.contrib.auth.views.LoginView`,
|
||||||
|
:class:`~django.contrib.auth.views.LogoutView`, and
|
||||||
|
:func:`~django.views.i18n.set_language` were subject to a potential
|
||||||
|
denial-of-service attack via certain inputs with a very large number of Unicode
|
||||||
|
characters.
|
||||||
|
|
||||||
Bugfixes
|
Bugfixes
|
||||||
========
|
========
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ from django.test import SimpleTestCase
|
|||||||
from django.utils.datastructures import MultiValueDict
|
from django.utils.datastructures import MultiValueDict
|
||||||
from django.utils.http import (
|
from django.utils.http import (
|
||||||
MAX_HEADER_LENGTH,
|
MAX_HEADER_LENGTH,
|
||||||
|
MAX_URL_LENGTH,
|
||||||
base36_to_int,
|
base36_to_int,
|
||||||
content_disposition_header,
|
content_disposition_header,
|
||||||
escape_leading_slashes,
|
escape_leading_slashes,
|
||||||
@ -274,6 +275,21 @@ class URLHasAllowedHostAndSchemeTests(unittest.TestCase):
|
|||||||
False,
|
False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_max_url_length(self):
|
||||||
|
allowed_host = "example.com"
|
||||||
|
max_extra_characters = "é" * (MAX_URL_LENGTH - len(allowed_host) - 1)
|
||||||
|
max_length_boundary_url = f"{allowed_host}/{max_extra_characters}"
|
||||||
|
cases = [
|
||||||
|
(max_length_boundary_url, True),
|
||||||
|
(max_length_boundary_url + "ú", False),
|
||||||
|
]
|
||||||
|
for url, expected in cases:
|
||||||
|
with self.subTest(url=url):
|
||||||
|
self.assertIs(
|
||||||
|
url_has_allowed_host_and_scheme(url, allowed_hosts={allowed_host}),
|
||||||
|
expected,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class URLSafeBase64Tests(unittest.TestCase):
|
class URLSafeBase64Tests(unittest.TestCase):
|
||||||
def test_roundtrip(self):
|
def test_roundtrip(self):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user