티스토리 수익 글 보기

티스토리 수익 글 보기

[6.0.x] Fixed CVE-2026-35193 — Varied on Authorization when caching … · django/django@664652f · GitHub
Skip to content

Commit 664652f

Browse files
jacobtylerwallsnessita
authored andcommitted
[6.0.x] Fixed CVE-2026-35193 — Varied on Authorization when caching non-public responses.
Thanks Shai Berger for the report, and Natalia Bidart and Sarah Boyce for reviews. Backport of a2faa8e from main.
1 parent b433025 commit 664652f

5 files changed

Lines changed: 72 additions & 0 deletions

File tree

django/middleware/cache.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
* This middleware also sets ETag, Last-Modified, Expires and Cache-Control
4242
headers on the response object.
4343
44+
* If the request had an Authorization header and the response was not marked
45+
"Cache-Control: public", the response will vary on Authorization.
4446
"""
4547

4648
import time
@@ -53,6 +55,7 @@
5355
has_vary_header,
5456
learn_cache_key,
5557
patch_response_headers,
58+
patch_vary_headers,
5659
)
5760
from django.utils.deprecation import MiddlewareMixin
5861
from django.utils.http import parse_http_date_safe
@@ -130,6 +133,12 @@ def process_response(self, request, response):
130133
# max-age was set to 0, don't cache.
131134
return response
132135
patch_response_headers(response, timeout)
136+
# Make the response vary on Authorization if the request bears that
137+
# header, unless allowed by "public" per RFC 9111, Section 3.5. No
138+
# exceptions are made for "s-maxage" and "must-revalidate" since these
139+
# are not currently implemented by Django.
140+
if request.headers.get("Authorization") and "public" not in cache_control:
141+
patch_vary_headers(response, ("Authorization",))
133142
if timeout and response.status_code == 200:
134143
cache_key = learn_cache_key(
135144
request, response, timeout, self.key_prefix, cache=self.cache

docs/releases/5.2.15.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,16 @@ where ``Cache-Control`` is set manually.
5151

5252
This issue has severity "low" according to the :ref:`Django security policy
5353
<severity-levels>`.
54+
55+
CVE-2026-35193: Potential exposure of private data via missing ``Vary: Authorization``
56+
======================================================================================
57+
58+
:class:`~django.middleware.cache.UpdateCacheMiddleware` and
59+
:func:`~django.views.decorators.cache.cache_page` decorator allowed responses
60+
to requests bearing an ``Authorization`` header (and without ``Cache-Control:
61+
public``) to be cached. To conform with the existing mechanism for constructing
62+
cache keys, responses to these requests will now :ref:`vary on
63+
<using-vary-headers>` ``Authorization``.
64+
65+
This issue has severity "low" according to the :ref:`Django security policy
66+
<severity-levels>`.

docs/releases/6.0.6.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,19 @@ where ``Cache-Control`` is set manually.
5353
This issue has severity "low" according to the :ref:`Django security policy
5454
<severity-levels>`.
5555

56+
CVE-2026-35193: Potential exposure of private data via missing ``Vary: Authorization``
57+
======================================================================================
58+
59+
:class:`~django.middleware.cache.UpdateCacheMiddleware` and
60+
:func:`~django.views.decorators.cache.cache_page` decorator allowed responses
61+
to requests bearing an ``Authorization`` header (and without ``Cache-Control:
62+
public``) to be cached. To conform with the existing mechanism for constructing
63+
cache keys, responses to these requests will now :ref:`vary on
64+
<using-vary-headers>` ``Authorization``.
65+
66+
This issue has severity "low" according to the :ref:`Django security policy
67+
<severity-levels>`.
68+
5669
Bugfixes
5770
========
5871

docs/topics/cache.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1403,6 +1403,18 @@ second argument.
14031403
For more on Vary headers, see the :rfc:`official Vary spec
14041404
<9110#section-12.5.5>`.
14051405

1406+
.. admonition:: ``CacheMiddleware`` varies on ``Authorization`` automatically
1407+
1408+
Although varying on ``Authorization`` is not strictly necessary given that
1409+
:rfc:`9111#section-3.5` allows caches to avoid reusing authenticated
1410+
responses, Django's ``CacheMiddleware`` adds ``Authorization`` to the
1411+
``Vary`` header to simplify construction of cache keys.
1412+
1413+
.. versionchanged:: 6.0.6
1414+
1415+
Previously, ``UpdateCacheMiddleware`` did not vary on ``Authorization`` for
1416+
requests bearing that header.
1417+
14061418
Controlling cache: Using other headers
14071419
======================================
14081420

tests/cache/tests.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
from django.utils import timezone, translation
5858
from django.utils.cache import (
5959
get_cache_key,
60+
has_vary_header,
6061
learn_cache_key,
6162
patch_cache_control,
6263
patch_vary_headers,
@@ -2776,6 +2777,30 @@ def test_vary_asterisk_not_cached(self):
27762777
response = view(request, "2")
27772778
self.assertEqual(response.content, b"Hello World 2")
27782779

2780+
def test_vary_on_authorization_for_authorization_header(self):
2781+
view_with_cache = cache_page(3)(hello_world_view)
2782+
request = self.factory.get("/view/", headers={"Authorization": "token"})
2783+
response = view_with_cache(request, "1")
2784+
self.assertIs(has_vary_header(response, "Authorization"), True)
2785+
2786+
def test_no_vary_on_authorization_for_empty_authorization_header(self):
2787+
view_with_cache = cache_page(3)(hello_world_view)
2788+
request = self.factory.get("/view/", headers={"Authorization": ""})
2789+
response = view_with_cache(request, "1")
2790+
self.assertIs(has_vary_header(response, "Authorization"), False)
2791+
2792+
def test_authorization_header_exceptions(self):
2793+
"""
2794+
Responses to requests with an ``Authorization`` header are not made to
2795+
vary on ``Authorization`` when ``Cache-Control: public`` is present.
2796+
``s-maxage`` and ``must-revalidate`` are also exceptions per RFC 9111,
2797+
Section 3.5, but Django does not implement them.
2798+
"""
2799+
view_with_cache = cache_page(3)(cache_control(public=True)(hello_world_view))
2800+
request = self.factory.get("/view/", headers={"Authorization": "token"})
2801+
response = view_with_cache(request, "1")
2802+
self.assertIs(has_vary_header(response, "Authorization"), False)
2803+
27792804
def test_sensitive_cookie_not_cached(self):
27802805
"""
27812806
Django must prevent caching of responses that set a user-specific (and

0 commit comments

Comments
 (0)