diff options
| author | Jake Howard <git@theorangeone.net> | 2026-05-12 16:29:56 +0100 |
|---|---|---|
| committer | Natalia <124304+nessita@users.noreply.github.com> | 2026-06-03 08:37:26 -0300 |
| commit | d618d7ae4fec727d5b582bd24f803c28d17bf7cd (patch) | |
| tree | 51fdf6600a8756f7c9065bfe8c81b91a696f2117 | |
| parent | df887f50198593a0e5b4638bfddbbd43a30fd276 (diff) | |
Fixed CVE-2026-8404 -- Used Cache-Control directives case-insensitively in UpdateCacheMiddleware.
Thanks Ahmed Badawe for the report, and Jacob Walls for reviews.
| -rw-r--r-- | django/middleware/cache.py | 4 | ||||
| -rw-r--r-- | docs/releases/5.2.15.txt | 16 | ||||
| -rw-r--r-- | docs/releases/6.0.6.txt | 16 | ||||
| -rw-r--r-- | tests/cache/tests.py | 16 |
4 files changed, 44 insertions, 8 deletions
diff --git a/django/middleware/cache.py b/django/middleware/cache.py index 678fde68a8..e70f427051 100644 --- a/django/middleware/cache.py +++ b/django/middleware/cache.py @@ -102,8 +102,8 @@ class UpdateCacheMiddleware(MiddlewareMixin): # Don't cache responses when the Cache-Control header is set to # private, no-cache, or no-store. - cache_control = response.get("Cache-Control", ()) - if any( + cache_control = response.get("Cache-Control", "").lower() + if cache_control and any( directive in cache_control for directive in ( "private", diff --git a/docs/releases/5.2.15.txt b/docs/releases/5.2.15.txt index 14796f8581..c068600280 100644 --- a/docs/releases/5.2.15.txt +++ b/docs/releases/5.2.15.txt @@ -35,3 +35,19 @@ Connections configured with :setting:`EMAIL_USE_SSL` are not affected. This issue has severity "low" according to the :ref:`Django security policy <severity-levels>`. + +CVE-2026-8404: Potential exposure of private data via case-sensitive ``Cache-Control`` directives +================================================================================================= + +:class:`~django.middleware.cache.UpdateCacheMiddleware` and +:func:`~django.views.decorators.cache.cache_page` incorrectly cached responses +marked with private ``Cache-Control`` directives when using mixed or uppercase +values (e.g. ``Private``). + +The :func:`~django.views.decorators.cache.cache_control` decorator and +:func:`~django.utils.cache.patch_cache_control` function were not affected, +since they normalize directives to lowercase. This issue only affects responses +where ``Cache-Control`` is set manually. + +This issue has severity "low" according to the :ref:`Django security policy +<severity-levels>`. diff --git a/docs/releases/6.0.6.txt b/docs/releases/6.0.6.txt index 20ae1db93c..afcbbe0eb2 100644 --- a/docs/releases/6.0.6.txt +++ b/docs/releases/6.0.6.txt @@ -37,6 +37,22 @@ Connections configured with :setting:`EMAIL_USE_SSL` are not affected. This issue has severity "low" according to the :ref:`Django security policy <severity-levels>`. +CVE-2026-8404: Potential exposure of private data via case-sensitive ``Cache-Control`` directives +================================================================================================= + +:class:`~django.middleware.cache.UpdateCacheMiddleware` and +:func:`~django.views.decorators.cache.cache_page` incorrectly cached responses +marked with private ``Cache-Control`` directives when using mixed or uppercase +values (e.g. ``Private``). + +The :func:`~django.views.decorators.cache.cache_control` decorator and +:func:`~django.utils.cache.patch_cache_control` function were not affected, +since they normalize directives to lowercase. This issue only affects responses +where ``Cache-Control`` is set manually. + +This issue has severity "low" according to the :ref:`Django security policy +<severity-levels>`. + Bugfixes ======== diff --git a/tests/cache/tests.py b/tests/cache/tests.py index 65ca885125..d4cfadb050 100644 --- a/tests/cache/tests.py +++ b/tests/cache/tests.py @@ -2851,15 +2851,19 @@ class CacheMiddlewareTest(SimpleTestCase): Responses with 'Cache-Control: private/no-cache/no-store' are not cached. """ - for cc in ("private", "no-cache", "no-store"): + for cc in ("private", "no-cache", "no-store", "PRIVATE", "NO-store"): with self.subTest(cache_control=cc): - view_with_cache = cache_page(3)( - cache_control(**{cc: True})(hello_world_view) - ) + # Cannot use @cache_control() as it lowercases directives. + @cache_page(3) + def view(request, value): + return HttpResponse( + f"Hello World {value}", headers={"Cache-Control": cc} + ) + request = self.factory.get("/view/") - response = view_with_cache(request, "1") + response = view(request, "1") self.assertEqual(response.content, b"Hello World 1") - response = view_with_cache(request, "2") + response = view(request, "2") self.assertEqual(response.content, b"Hello World 2") def test_vary_asterisk_not_cached(self): |
