티스토리 수익 글 보기

티스토리 수익 글 보기

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

Commit 050a3dc

Browse files
jacobtylerwallsnessita
authored andcommitted
[5.2.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 366d9ae commit 050a3dc

4 files changed

Lines changed: 59 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/topics/cache.txt

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

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

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,
@@ -2767,6 +2768,30 @@ def test_vary_asterisk_not_cached(self):
27672768
response = view(request, "2")
27682769
self.assertEqual(response.content, b"Hello World 2")
27692770

2771+
def test_vary_on_authorization_for_authorization_header(self):
2772+
view_with_cache = cache_page(3)(hello_world_view)
2773+
request = self.factory.get("/view/", headers={"Authorization": "token"})
2774+
response = view_with_cache(request, "1")
2775+
self.assertIs(has_vary_header(response, "Authorization"), True)
2776+
2777+
def test_no_vary_on_authorization_for_empty_authorization_header(self):
2778+
view_with_cache = cache_page(3)(hello_world_view)
2779+
request = self.factory.get("/view/", headers={"Authorization": ""})
2780+
response = view_with_cache(request, "1")
2781+
self.assertIs(has_vary_header(response, "Authorization"), False)
2782+
2783+
def test_authorization_header_exceptions(self):
2784+
"""
2785+
Responses to requests with an ``Authorization`` header are not made to
2786+
vary on ``Authorization`` when ``Cache-Control: public`` is present.
2787+
``s-maxage`` and ``must-revalidate`` are also exceptions per RFC 9111,
2788+
Section 3.5, but Django does not implement them.
2789+
"""
2790+
view_with_cache = cache_page(3)(cache_control(public=True)(hello_world_view))
2791+
request = self.factory.get("/view/", headers={"Authorization": "token"})
2792+
response = view_with_cache(request, "1")
2793+
self.assertIs(has_vary_header(response, "Authorization"), False)
2794+
27702795
def test_sensitive_cookie_not_cached(self):
27712796
"""
27722797
Django must prevent caching of responses that set a user-specific (and

0 commit comments

Comments
 (0)