#37095: URL redirect max length check should check %-encoded value
-------------------------------------------+-----------------------------
               Reporter:  Jacob Walls      |          Owner:  Jacob Walls
                   Type:  Bug              |         Status:  assigned
              Component:  HTTP handling    |        Version:  dev
               Severity:  Release blocker  |       Keywords:
           Triage Stage:  Unreviewed       |      Has patch:  0
    Needs documentation:  0                |    Needs tests:  0
Patch needs improvement:  0                |  Easy pickings:  0
                  UI/UX:  0                |
-------------------------------------------+-----------------------------
 In #36767 we made configurable the length check on redirect URLs we added
 for CVE-2025-64458. "Cowork" sent us a flurry of nuisance security reports
 last night, but among them was a reasonable suggestion that we apply the
 length check against the percent-encoded URI:

 {{{#!diff
 diff --git a/django/http/response.py b/django/http/response.py
 index 45fb0177d1..0a61fb723f 100644
 --- a/django/http/response.py
 +++ b/django/http/response.py
 @@ -641,12 +641,13 @@ class HttpResponseRedirectBase(HttpResponse):
          **kwargs,
      ):
          super().__init__(*args, **kwargs)
 -        self["Location"] = iri_to_uri(redirect_to)
          redirect_to_str = str(redirect_to)
 -        if max_length is not None and len(redirect_to_str) > max_length:
 +        location = iri_to_uri(redirect_to_str)
 +        if max_length is not None and len(location) > max_length:
              raise DisallowedRedirect(
                  f"Unsafe redirect exceeding {max_length} characters"
              )
 +        self["Location"] = location
          parsed = urlsplit(redirect_to_str)
          if preserve_request:
              self.status_code = self.status_code_preserve_request
 diff --git a/tests/httpwrappers/tests.py b/tests/httpwrappers/tests.py
 index b990e9f816..3430dbbcd3 100644
 --- a/tests/httpwrappers/tests.py
 +++ b/tests/httpwrappers/tests.py
 @@ -24,6 +24,7 @@ from django.http import (
      parse_cookie,
  )
  from django.test import SimpleTestCase
 +from django.utils.encoding import iri_to_uri
  from django.utils.functional import lazystr
  from django.utils.http import MAX_URL_REDIRECT_LENGTH

 @@ -498,6 +499,29 @@ class HttpResponseTests(SimpleTestCase):
                  response = response_class(long_url)
                  self.assertEqual(response.url, long_url)

 +    def test_redirect_url_max_length_checks_encoded_location(self):
 +        long_url = "/" + "é" * (MAX_URL_REDIRECT_LENGTH - 1)
 +        self.assertLessEqual(len(long_url), MAX_URL_REDIRECT_LENGTH)
 +        self.assertGreater(len(iri_to_uri(long_url)),
 MAX_URL_REDIRECT_LENGTH)
 +        for response_class in (HttpResponseRedirect,
 HttpResponsePermanentRedirect):
 +            with (
 +                self.subTest(response_class=response_class),
 +                self.assertRaisesMessage(
 +                    DisallowedRedirect,
 +                    f"Unsafe redirect exceeding {MAX_URL_REDIRECT_LENGTH}
 characters",
 +                ),
 +            ):
 +                response_class(long_url)
 +
 +    def
 test_redirect_url_max_length_allows_encoded_location_at_limit(self):
 +        redirect_to = "/" + "é" * ((MAX_URL_REDIRECT_LENGTH - 1) // 6)
 +        location = iri_to_uri(redirect_to)
 +        self.assertLessEqual(len(location), MAX_URL_REDIRECT_LENGTH)
 +        for response_class in (HttpResponseRedirect,
 HttpResponsePermanentRedirect):
 +            with self.subTest(response_class=response_class):
 +                response = response_class(redirect_to)
 +                self.assertEqual(response.url, location)
 +
      def test_redirect_url_max_length_override_via_param(self):
          base_url = "https://example.com/";
          for (max_length, length), response_class in itertools.product(
 }}}

 Although we didn't take this as a security issue (the limit we apply on
 the raw value is good enough for DoS), this seems like a functionality bug
 to fix before the release.
-- 
Ticket URL: <https://code.djangoproject.com/ticket/37095>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/django-updates/0107019e18c6db19-80e94a51-1f75-4d6f-86c9-2f21e9d0eaa3-000000%40eu-central-1.amazonses.com.

Reply via email to