CVE-2025-27556
Description
An issue was discovered in Django 5.1 before 5.1.8 and 5.0 before 5.0.14. The NFKC normalization is slow on Windows. As a consequence, django.contrib.auth.views.LoginView, django.contrib.auth.views.LogoutView, and django.views.i18n.set_language are subject to a potential denial-of-service attack via certain inputs with a very large number of Unicode characters.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
DjangoPyPI | >= 5.0, < 5.0.14 | 5.0.14 |
DjangoPyPI | >= 5.1, < 5.1.8 | 5.1.8 |
Affected products
1- Range: 5.0
Patches
439e2297210d9Fixed CVE-2025-27556 -- Mitigated potential DoS in url_has_allowed_host_and_scheme() on Windows.
6 files changed · +44 −4
django/core/validators.py+2 −1 modified@@ -6,6 +6,7 @@ from django.core.exceptions import ValidationError 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.regex_helper import _lazy_re_compile from django.utils.translation import gettext_lazy as _ @@ -152,7 +153,7 @@ class URLValidator(RegexValidator): message = _("Enter a valid URL.") schemes = ["http", "https", "ftp", "ftps"] unsafe_chars = frozenset("\t\r\n") - max_length = 2048 + max_length = MAX_URL_LENGTH def __init__(self, schemes=None, **kwargs): super().__init__(**kwargs)
django/utils/html.py+1 −2 modified@@ -13,7 +13,7 @@ from django.core.validators import EmailValidator from django.utils.deprecation import RemovedInDjango70Warning 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.safestring import SafeData, SafeString, mark_safe from django.utils.text import normalize_newlines @@ -41,7 +41,6 @@ ) ) -MAX_URL_LENGTH = 2048 MAX_STRIP_TAGS_DEPTH = 50
django/utils/http.py+5 −1 modified@@ -39,6 +39,7 @@ RFC3986_GENDELIMS = ":/?#[]@" RFC3986_SUBDELIMS = "!$&'()*+,;=" +MAX_URL_LENGTH = 2048 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): # 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. - 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 try: url_info = urlsplit(url)
docs/releases/5.0.14.txt+10 −0 modified@@ -5,3 +5,13 @@ Django 5.0.14 release notes *April 2, 2025* 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.
docs/releases/5.1.8.txt+10 −0 modified@@ -7,6 +7,16 @@ Django 5.1.8 release notes Django 5.1.8 fixes a security issue with severity "moderate" and several bugs 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 ========
tests/utils_tests/test_http.py+16 −0 modified@@ -7,6 +7,7 @@ from django.utils.datastructures import MultiValueDict from django.utils.http import ( MAX_HEADER_LENGTH, + MAX_URL_LENGTH, base36_to_int, content_disposition_header, escape_leading_slashes, @@ -274,6 +275,21 @@ def test_secure_param_non_https_urls(self): 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): def test_roundtrip(self):
2cb311f7b069[5.2.x] Fixed CVE-2025-27556 -- Mitigated potential DoS in url_has_allowed_host_and_scheme() on Windows.
6 files changed · +44 −4
django/core/validators.py+2 −1 modified@@ -6,6 +6,7 @@ from django.core.exceptions import ValidationError 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.regex_helper import _lazy_re_compile from django.utils.translation import gettext_lazy as _ @@ -152,7 +153,7 @@ class URLValidator(RegexValidator): message = _("Enter a valid URL.") schemes = ["http", "https", "ftp", "ftps"] unsafe_chars = frozenset("\t\r\n") - max_length = 2048 + max_length = MAX_URL_LENGTH def __init__(self, schemes=None, **kwargs): super().__init__(**kwargs)
django/utils/html.py+1 −2 modified@@ -12,7 +12,7 @@ from django.core.validators import EmailValidator from django.utils.deprecation import RemovedInDjango60Warning 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.safestring import SafeData, SafeString, mark_safe from django.utils.text import normalize_newlines @@ -40,7 +40,6 @@ ) ) -MAX_URL_LENGTH = 2048 MAX_STRIP_TAGS_DEPTH = 50
django/utils/http.py+5 −1 modified@@ -37,6 +37,7 @@ RFC3986_GENDELIMS = ":/?#[]@" RFC3986_SUBDELIMS = "!$&'()*+,;=" +MAX_URL_LENGTH = 2048 def urlencode(query, doseq=False): @@ -272,7 +273,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): # 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. - 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 try: url_info = urlsplit(url)
docs/releases/5.0.14.txt+10 −0 modified@@ -5,3 +5,13 @@ Django 5.0.14 release notes *April 2, 2025* 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.
docs/releases/5.1.8.txt+10 −0 modified@@ -7,6 +7,16 @@ Django 5.1.8 release notes Django 5.1.8 fixes a security issue with severity "moderate" and several bugs 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 ========
tests/utils_tests/test_http.py+16 −0 modified@@ -6,6 +6,7 @@ from django.test import SimpleTestCase from django.utils.datastructures import MultiValueDict from django.utils.http import ( + MAX_URL_LENGTH, base36_to_int, content_disposition_header, escape_leading_slashes, @@ -273,6 +274,21 @@ def test_secure_param_non_https_urls(self): 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): def test_roundtrip(self):
8c6871b097b6[5.0.x] Fixed CVE-2025-27556 -- Mitigated potential DoS in url_has_allowed_host_and_scheme() on Windows.
5 files changed · +34 −4
django/core/validators.py+2 −1 modified@@ -7,6 +7,7 @@ from django.core.exceptions import ValidationError from django.utils.deconstruct import deconstructible from django.utils.encoding import punycode +from django.utils.http import MAX_URL_LENGTH from django.utils.ipv6 import is_valid_ipv6_address from django.utils.regex_helper import _lazy_re_compile from django.utils.translation import gettext_lazy as _ @@ -104,7 +105,7 @@ class URLValidator(RegexValidator): message = _("Enter a valid URL.") schemes = ["http", "https", "ftp", "ftps"] unsafe_chars = frozenset("\t\r\n") - max_length = 2048 + max_length = MAX_URL_LENGTH def __init__(self, schemes=None, **kwargs): super().__init__(**kwargs)
django/utils/html.py+1 −2 modified@@ -11,7 +11,7 @@ from django.utils.deprecation import RemovedInDjango60Warning from django.utils.encoding import punycode 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.safestring import SafeData, SafeString, mark_safe from django.utils.text import normalize_newlines @@ -37,7 +37,6 @@ "spacer", } -MAX_URL_LENGTH = 2048 MAX_STRIP_TAGS_DEPTH = 50
django/utils/http.py+5 −1 modified@@ -37,6 +37,7 @@ RFC3986_GENDELIMS = ":/?#[]@" RFC3986_SUBDELIMS = "!$&'()*+,;=" +MAX_URL_LENGTH = 2048 def urlencode(query, doseq=False): @@ -273,7 +274,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): # Chrome considers any URL with more than two slashes to be absolute, but # urlparse is not so flexible. Treat any url with three slashes as unsafe. - if url.startswith("///"): + if url.startswith("///") or len(url) > MAX_URL_LENGTH: + # urlparse 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 try: url_info = urlparse(url)
docs/releases/5.0.14.txt+10 −0 modified@@ -5,3 +5,13 @@ Django 5.0.14 release notes *April 2, 2025* 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.
tests/utils_tests/test_http.py+16 −0 modified@@ -6,6 +6,7 @@ from django.test import SimpleTestCase from django.utils.datastructures import MultiValueDict from django.utils.http import ( + MAX_URL_LENGTH, base36_to_int, content_disposition_header, escape_leading_slashes, @@ -273,6 +274,21 @@ def test_secure_param_non_https_urls(self): 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): def test_roundtrip(self):
edc2716d01a6[5.1.x] Fixed CVE-2025-27556 -- Mitigated potential DoS in url_has_allowed_host_and_scheme() on Windows.
6 files changed · +44 −4
django/core/validators.py+2 −1 modified@@ -7,6 +7,7 @@ from django.core.exceptions import ValidationError from django.utils.deconstruct import deconstructible from django.utils.encoding import punycode +from django.utils.http import MAX_URL_LENGTH from django.utils.ipv6 import is_valid_ipv6_address from django.utils.regex_helper import _lazy_re_compile from django.utils.translation import gettext_lazy as _ @@ -155,7 +156,7 @@ class URLValidator(RegexValidator): message = _("Enter a valid URL.") schemes = ["http", "https", "ftp", "ftps"] unsafe_chars = frozenset("\t\r\n") - max_length = 2048 + max_length = MAX_URL_LENGTH def __init__(self, schemes=None, **kwargs): super().__init__(**kwargs)
django/utils/html.py+1 −2 modified@@ -11,7 +11,7 @@ from django.utils.deprecation import RemovedInDjango60Warning from django.utils.encoding import punycode 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.safestring import SafeData, SafeString, mark_safe from django.utils.text import normalize_newlines @@ -39,7 +39,6 @@ ) ) -MAX_URL_LENGTH = 2048 MAX_STRIP_TAGS_DEPTH = 50
django/utils/http.py+5 −1 modified@@ -37,6 +37,7 @@ RFC3986_GENDELIMS = ":/?#[]@" RFC3986_SUBDELIMS = "!$&'()*+,;=" +MAX_URL_LENGTH = 2048 def urlencode(query, doseq=False): @@ -272,7 +273,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): # Chrome considers any URL with more than two slashes to be absolute, but # urlparse is not so flexible. Treat any url with three slashes as unsafe. - if url.startswith("///"): + if url.startswith("///") or len(url) > MAX_URL_LENGTH: + # urlparse 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 try: url_info = urlparse(url)
docs/releases/5.0.14.txt+10 −0 modified@@ -5,3 +5,13 @@ Django 5.0.14 release notes *April 2, 2025* 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.
docs/releases/5.1.8.txt+10 −0 modified@@ -7,6 +7,16 @@ Django 5.1.8 release notes Django 5.1.8 fixes a security issue with severity "moderate" and several bugs 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 ========
tests/utils_tests/test_http.py+16 −0 modified@@ -6,6 +6,7 @@ from django.test import SimpleTestCase from django.utils.datastructures import MultiValueDict from django.utils.http import ( + MAX_URL_LENGTH, base36_to_int, content_disposition_header, escape_leading_slashes, @@ -273,6 +274,21 @@ def test_secure_param_non_https_urls(self): 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): def test_roundtrip(self):
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
13- github.com/advisories/GHSA-wqfg-m96j-85vmghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-27556ghsaADVISORY
- www.openwall.com/lists/oss-security/2025/04/02/2ghsaWEB
- docs.djangoproject.com/en/dev/releases/securityghsaWEB
- github.com/django/django/commit/2cb311f7b069723027fb5def4044d1816d7d2afdghsaWEB
- github.com/django/django/commit/39e2297210d9d2938c75fc911d45f0e863dc4821ghsaWEB
- github.com/django/django/commit/8c6871b097b6c49d2a782c0d80d908bcbe2116f1ghsaWEB
- github.com/django/django/commit/edc2716d01a6fdd84b173c02031695231bcee1f8ghsaWEB
- github.com/pypa/advisory-database/tree/main/vulns/django/PYSEC-2025-14.yamlghsaWEB
- groups.google.com/g/django-announceghsaWEB
- www.djangoproject.com/weblog/2025/apr/02/security-releasesghsaWEB
- docs.djangoproject.com/en/dev/releases/security/mitre
- www.djangoproject.com/weblog/2025/apr/02/security-releases/mitre
News mentions
0No linked articles in our index yet.