Skip to content

Commit e697349

Browse files
varunkasyapnessita
authored andcommitted
[4.2.x] 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. Backport of a8cf8c2 from main.
1 parent 7d7f27b commit e697349

File tree

3 files changed

+23
-5
lines changed

3 files changed

+23
-5
lines changed

django/http/response.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@
2121
from django.utils import timezone
2222
from django.utils.datastructures import CaseInsensitiveMapping
2323
from django.utils.encoding import iri_to_uri
24-
from django.utils.http import MAX_URL_LENGTH, content_disposition_header, http_date
24+
from django.utils.http import (
25+
MAX_URL_REDIRECT_LENGTH,
26+
content_disposition_header,
27+
http_date,
28+
)
2529
from django.utils.regex_helper import _lazy_re_compile
2630

2731
_charset_from_content_type_re = _lazy_re_compile(
@@ -615,9 +619,9 @@ def __init__(self, redirect_to, *args, **kwargs):
615619
super().__init__(*args, **kwargs)
616620
self["Location"] = iri_to_uri(redirect_to)
617621
redirect_to_str = str(redirect_to)
618-
if len(redirect_to_str) > MAX_URL_LENGTH:
622+
if len(redirect_to_str) > MAX_URL_REDIRECT_LENGTH:
619623
raise DisallowedRedirect(
620-
f"Unsafe redirect exceeding {MAX_URL_LENGTH} characters"
624+
f"Unsafe redirect exceeding {MAX_URL_REDIRECT_LENGTH} characters"
621625
)
622626
parsed = urlparse(redirect_to_str)
623627
if parsed.scheme and parsed.scheme not in self.allowed_schemes:

django/utils/http.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
RFC3986_GENDELIMS = ":/?#[]@"
4949
RFC3986_SUBDELIMS = "!$&'()*+,;="
5050
MAX_URL_LENGTH = 2048
51+
MAX_URL_REDIRECT_LENGTH = 16384
5152

5253
# TODO: Remove when dropping support for PY38.
5354
# Unsafe bytes to be removed per WHATWG spec.

tests/httpwrappers/tests.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
)
2525
from django.test import SimpleTestCase
2626
from django.utils.functional import lazystr
27-
from django.utils.http import MAX_URL_LENGTH
27+
from django.utils.http import MAX_URL_REDIRECT_LENGTH
2828

2929

3030
class QueryDictTests(SimpleTestCase):
@@ -486,12 +486,25 @@ def test_stream_interface(self):
486486
r.writelines(["foo\n", "bar\n", "baz\n"])
487487
self.assertEqual(r.content, b"foo\nbar\nbaz\n")
488488

489+
def test_redirect_url_max_length(self):
490+
base_url = "https://example.com/"
491+
for length in (
492+
MAX_URL_REDIRECT_LENGTH - 1,
493+
MAX_URL_REDIRECT_LENGTH,
494+
):
495+
long_url = base_url + "x" * (length - len(base_url))
496+
with self.subTest(length=length):
497+
response = HttpResponseRedirect(long_url)
498+
self.assertEqual(response.url, long_url)
499+
response = HttpResponsePermanentRedirect(long_url)
500+
self.assertEqual(response.url, long_url)
501+
489502
def test_unsafe_redirect(self):
490503
bad_urls = [
491504
'data:text/html,<script>window.alert("xss")</script>',
492505
493506
"file:///etc/passwd",
494-
"é" * (MAX_URL_LENGTH + 1),
507+
"é" * (MAX_URL_REDIRECT_LENGTH + 1),
495508
]
496509
for url in bad_urls:
497510
with self.assertRaises(DisallowedRedirect):

0 commit comments

Comments
 (0)