Fixed #36743 -- Increased URL max length enforced in HttpResponseRedirectBase.

Refs CVE-2025-64458.

The previous limit of 2048 characters reused the URLValidator constant
and proved too restrictive for legitimate redirects to some third-party
services. This change introduces a separate `MAX_URL_REDIRECT_LENGTH`
constant (defaulting to 16384) and uses it in HttpResponseRedirectBase.

Thanks Jacob Walls for report and review.
This commit is contained in:
varunkasyap
2025-11-26 14:28:24 -03:00
committed by nessita
parent 818a620f08
commit a8cf8c292c
4 changed files with 28 additions and 5 deletions

View File

@@ -22,7 +22,11 @@ from django.utils import timezone
from django.utils.datastructures import CaseInsensitiveMapping
from django.utils.encoding import iri_to_uri
from django.utils.functional import cached_property
from django.utils.http import MAX_URL_LENGTH, content_disposition_header, http_date
from django.utils.http import (
MAX_URL_REDIRECT_LENGTH,
content_disposition_header,
http_date,
)
from django.utils.regex_helper import _lazy_re_compile
_charset_from_content_type_re = _lazy_re_compile(
@@ -632,9 +636,9 @@ class HttpResponseRedirectBase(HttpResponse):
super().__init__(*args, **kwargs)
self["Location"] = iri_to_uri(redirect_to)
redirect_to_str = str(redirect_to)
if len(redirect_to_str) > MAX_URL_LENGTH:
if len(redirect_to_str) > MAX_URL_REDIRECT_LENGTH:
raise DisallowedRedirect(
f"Unsafe redirect exceeding {MAX_URL_LENGTH} characters"
f"Unsafe redirect exceeding {MAX_URL_REDIRECT_LENGTH} characters"
)
parsed = urlsplit(redirect_to_str)
if preserve_request:

View File

@@ -39,6 +39,7 @@ ASCTIME_DATE = _lazy_re_compile(r"^\w{3} %s %s %s %s$" % (__M, __D2, __T, __Y))
RFC3986_GENDELIMS = ":/?#[]@"
RFC3986_SUBDELIMS = "!$&'()*+,;="
MAX_URL_LENGTH = 2048
MAX_URL_REDIRECT_LENGTH = 16384
def urlencode(query, doseq=False):

View File

@@ -21,3 +21,8 @@ Bugfixes
* Fixed a regression in Django 5.2.2 that caused a crash when using aggregate
functions with an empty ``Q`` filter over a queryset with annotations
(:ticket:`36751`).
* Fixed a regression in Django 5.2.8 where ``DisallowedRedirect`` was raised by
:class:`~django.http.HttpResponseRedirect` and
:class:`~django.http.HttpResponsePermanentRedirect` for URLs longer than 2048
characters. The limit is now 16384 characters (:ticket:`36743`).

View File

@@ -24,7 +24,7 @@ from django.http import (
)
from django.test import SimpleTestCase
from django.utils.functional import lazystr
from django.utils.http import MAX_URL_LENGTH
from django.utils.http import MAX_URL_REDIRECT_LENGTH
class QueryDictTests(SimpleTestCase):
@@ -486,12 +486,25 @@ class HttpResponseTests(SimpleTestCase):
r.writelines(["foo\n", "bar\n", "baz\n"])
self.assertEqual(r.content, b"foo\nbar\nbaz\n")
def test_redirect_url_max_length(self):
base_url = "https://example.com/"
for length in (
MAX_URL_REDIRECT_LENGTH - 1,
MAX_URL_REDIRECT_LENGTH,
):
long_url = base_url + "x" * (length - len(base_url))
with self.subTest(length=length):
response = HttpResponseRedirect(long_url)
self.assertEqual(response.url, long_url)
response = HttpResponsePermanentRedirect(long_url)
self.assertEqual(response.url, long_url)
def test_unsafe_redirect(self):
bad_urls = [
'data:text/html,<script>window.alert("xss")</script>',
"mailto:test@example.com",
"file:///etc/passwd",
"é" * (MAX_URL_LENGTH + 1),
"é" * (MAX_URL_REDIRECT_LENGTH + 1),
]
for url in bad_urls:
with self.assertRaises(DisallowedRedirect):