mirror of
https://github.com/django/django.git
synced 2026-02-09 02:49:25 +08:00
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:
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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`).
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user