CVE-2025-64458
Denial-of-service in Django redirects on Windows via slow Unicode input.
- CVSS 7.5
- CWE-407
- Resource Management
- Remote
An issue was discovered in 5.1 before 5.1.14, 4.2 before 4.2.26, and 5.2 before 5.2.8. NFKC normalization in Python is slow on Windows. As a consequence, `django.http.HttpResponseRedirect`, `django.http.HttpResponsePermanentRedirect`, and the shortcut `django.shortcuts.redirect` were subject to a potential denial-of-service attack via certain inputs with a very large number of Unicode characters. Earlier, unsupported Django series (such as 5.0.x, 4.1.x, and 3.2.x) were not evaluated and may also be affected. Django would like to thank Seokchan Yoon for reporting this issue.
- CWE
- CWE-407
- CVSS base score
- 7.5
- Published
- 2025-11-05
- OWASP
- A04 Insecure Design
- Orthogonal defect classification
- Algorithm
- Code defect classification
- Incorrect Algorithm
- Category
- Resource Management
- Subcategory
- Resource Exhaustion
- Accessibility scope
- Remote
- Impact
- Denial of Service (DoS)
- Affected component
- Django
- Fixed by upgrading
- Yes
Solution
Upgrade to Django 5.2.8, 5.1.14, or 4.2.26.
Vulnerable code sample
# This code demonstrates the vulnerability described in CVE-2025-64458
# by simulating the vulnerable components of an older Django version.
# The vulnerability lies in a slow validation step within the redirect process
# that can be exploited for a Denial-of-Service attack.
import sys
import time
from urllib.parse import urlsplit
# On Windows, the underlying Python implementation of unicodedata.normalize('NFKC', ...)
# can have quadratic complexity for certain inputs, which is used by urlsplit for
# Internationalized Domain Names (IDNA) processing. This script may not demonstrate
# the severe slowdown on non-Windows platforms.
# --- Simplified representation of vulnerable Django framework components ---
def iri_to_uri(iri):
"""A placeholder for Django's iri_to_uri function."""
# The actual implementation is more complex, but this is sufficient for the demo.
return str(iri)
def url_has_allowed_scheme_and_netloc(url, allowed_schemes):
"""
Simplified representation of the Django utility function from vulnerable versions.
This function was called inside HttpResponseRedirect.__init__ and was the
source of the vulnerability.
"""
if not url:
return False
try:
# VULNERABLE STEP: urlsplit() is called on the user-provided URL.
# For a URL containing a hostname with a large number of specific
# Unicode characters (like U+0130), this call can be extremely slow
# on certain platforms (notably Windows) due to NFKC normalization.
url_info = urlsplit(url)
# The original function performed more checks here, but they are
# not relevant to demonstrating the performance issue.
return True
except ValueError:
# The input URL was malformed.
return False
class HttpResponse:
"""Simplified base class for an HTTP response."""
def __init__(self, *args, **kwargs):
self.headers = {}
self.status_code = 200
def __setitem__(self, header, value):
self.headers[header] = value
class HttpResponseRedirectBase(HttpResponse):
"""
Simplified representation of the HttpResponseRedirectBase class as it
existed in vulnerable Django versions (e.g., 5.1 before 5.1.14).
"""
allowed_schemes = ["http", "https", "ftp"]
def __init__(self, redirect_to, *args, **kwargs):
super().__init__(*args, **kwargs)
self["Location"] = iri_to_uri(redirect_to)
# THE VULNERABLE CALL:
# In vulnerable versions, the redirect URL was validated using this function.
# The fix for the CVE removed this call from the __init__ method.
if not url_has_allowed_scheme_and_netloc(redirect_to, self.allowed_schemes):
# In a real app, this would raise a DisallowedRedirect exception.
# We'll just print a message for this demonstration.
print("Warning: Disallowed redirect scheme or netloc.", file=sys.stderr)
class HttpResponseRedirect(HttpResponseRedirectBase):
"""Simplified representation of the final redirect class."""
status_code = 302
def redirect(to):
"""
Simplified representation of the django.shortcuts.redirect shortcut,
which is the common entry point for this vulnerability.
"""
return HttpResponseRedirect(to)
# --- Demonstration of the Denial-of-Service attack ---
def vulnerable_view(request_url):
"""
Simulates a Django view that performs a redirect based on user input,
e.g., redirecting after login using a 'next' query parameter.
"""
print(f"View received request to redirect to: {request_url[:70]}...")
start_time = time.time()
# This call initiates the vulnerable chain: redirect() -> HttpResponseRedirect()
# -> HttpResponseRedirectBase.__init__() -> url_has_allowed_scheme_and_netloc() -> urlsplit()
response = redirect(request_url)
end_time = time.time()
duration = end_time - start_time
print(f"Redirect response generated in {duration:.4f} seconds.")
if duration > 2.0:
print("\n[!] The request took an unusually long time to process.")
print("[!] This demonstrates the Denial-of-Service vulnerability.")
return response
if __name__ == "__main__":
# The payload uses a character (U+0130) known to cause performance issues
# in NFKC normalization on some platforms.
malicious_character = "\u0130"
# A large number of these characters are used to amplify the effect.
# On a vulnerable system (Python on Windows), even 20,000 can cause
# a significant delay (many seconds or minutes).
payload_size = 30000
host_payload = malicious_character * payload_size
# The attacker crafts a URL where the slow-to-process payload is in the
# hostname part, as this is what urlsplit will attempt to normalize.
attack_url = f"http://{host_payload}.example.com/"
print("--- Demonstrating CVE-2025-64458 (pre-patch) ---")
print(f"Platform: {sys.platform}")
print(f"Using a payload of {payload_size} instances of character '\\u0130'.")
print("-" * 50)
vulnerable_view(attack_url)Patched code sample
import unicodedata
from urllib.parse import quote, urlsplit, urlunsplit
# This code is a hypothetical representation of the fix for CVE-2025-64458,
# as the CVE number itself is not real. The logic demonstrates a plausible
# fix based on the vulnerability's description.
# The vulnerability is that NFKC normalization is slow on very long strings,
# leading to a Denial-of-Service. The fix is to limit the input length
# before performing the expensive normalization. A common URL length limit
# is ~2048 characters.
URL_MAX_LENGTH = 2048
def _patched_iri_to_uri(iri):
"""
Represents the fixed version of a utility function that converts an
IRI (Internationalized Resource Identifier) to a URI (Uniform
Resource Identifier).
The fix for CVE-2025-64458 involves preventing the expensive NFKC
normalization on excessively long strings by enforcing a length limit.
"""
if iri is None:
return iri
# --- THE FIX ---
# Check the length of the input before performing any costly operations.
# If the URL is too long, raise an error immediately instead of attempting
# to process it, which prevents the DoS attack.
if len(iri) > URL_MAX_LENGTH:
raise ValueError("The length of the URL exceeds the allowed limit.")
# --- END OF FIX ---
# The original, potentially slow operation now only runs on inputs of a
# sanitized, reasonable length. This code simulates the logic found in
# Django's django.utils.encoding.iri_to_uri.
scheme, netloc, path, query, fragment = urlsplit(iri)
# Django's implementation is more complex, but the key is that NFKC
# normalization happens on parts of the URL.
if path:
# The expensive operation that causes the DoS on large inputs.
path = quote(unicodedata.normalize("NFKC", path).encode("utf-8"), safe="/%")
if query:
query = quote(unicodedata.normalize("NFKC", query).encode("utf-8"), safe="=&%?")
# Reassemble the URI
return urlunsplit((scheme, netloc, path, query, fragment))
class HttpResponseBase:
"""A mock base class to simulate Django's HttpResponse."""
def __init__(self, content_type=None, status=200, reason=None, charset=None):
self.headers = {}
self.status_code = status
def __setitem__(self, header, value):
self.headers[header] = value
class PatchedHttpResponseRedirect(HttpResponseBase):
"""
A mock of Django's HttpResponseRedirect demonstrating the patched behavior.
"""
status_code = 302
def __init__(self, redirect_to, *args, **kwargs):
super().__init__(*args, **kwargs)
# The 'redirect_to' URL is now sanitized by the patched function
# before being set as the 'Location' header.
self["Location"] = _patched_iri_to_uri(redirect_to)
def patched_redirect(to, *args, **kwargs):
"""
A mock of Django's redirect shortcut using the patched redirect class.
"""
return PatchedHttpResponseRedirect(to, *args, **kwargs)
# --- Demonstration of the fix ---
# 1. A normal, valid URL works as expected.
safe_url = "https://example.com/path/✓"
try:
response = patched_redirect(safe_url)
# print(f"Successfully processed safe URL. Location: {response.headers['Location']}")
except ValueError as e:
# print(f"Error processing safe URL: {e}")
pass
# 2. An excessively long URL that would have caused a DoS.
# Create a string with a large number of Unicode characters.
# The '①' character is one that requires NFKC normalization.
maliciously_long_url = "https://example.com/" + "①" * (URL_MAX_LENGTH + 1)
try:
# This call will now fail fast due to the length check in _patched_iri_to_uri.
response = patched_redirect(maliciously_long_url)
except ValueError as e:
# The fix correctly raises a ValueError, preventing the DoS.
# print(f"Correctly blocked malicious URL with error: {e}")
passPayload
'\u1158' * 100000
Cite this entry
@misc{vaitp:cve202564458,
title = {{Denial-of-service in Django redirects on Windows via slow Unicode input.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2025},
note = {VAITP Python Vulnerability Dataset, entry CVE-2025-64458},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-64458/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
