티스토리 수익 글 보기

티스토리 수익 글 보기

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

Commit b7b23f4

Browse files
jacobtylerwallsnessita
authored andcommitted
[6.1.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 130467c commit b7b23f4

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
@@ -58,6 +58,7 @@
5858
from django.utils import timezone, translation
5959
from django.utils.cache import (
6060
get_cache_key,
61+
has_vary_header,
6162
learn_cache_key,
6263
patch_cache_control,
6364
patch_vary_headers,
@@ -2879,6 +2880,30 @@ def test_vary_asterisk_not_cached(self):
28792880
response = view(request, "2")
28802881
self.assertEqual(response.content, b"Hello World 2")
28812882

2883+
def test_vary_on_authorization_for_authorization_header(self):
2884+
view_with_cache = cache_page(3)(hello_world_view)
2885+
request = self.factory.get("/view/", headers={"Authorization": "token"})
2886+
response = view_with_cache(request, "1")
2887+
self.assertIs(has_vary_header(response, "Authorization"), True)
2888+
2889+
def test_no_vary_on_authorization_for_empty_authorization_header(self):
2890+
view_with_cache = cache_page(3)(hello_world_view)
2891+
request = self.factory.get("/view/", headers={"Authorization": ""})
2892+
response = view_with_cache(request, "1")
2893+
self.assertIs(has_vary_header(response, "Authorization"), False)
2894+
2895+
def test_authorization_header_exceptions(self):
2896+
"""
2897+
Responses to requests with an ``Authorization`` header are not made to
2898+
vary on ``Authorization`` when ``Cache-Control: public`` is present.
2899+
``s-maxage`` and ``must-revalidate`` are also exceptions per RFC 9111,
2900+
Section 3.5, but Django does not implement them.
2901+
"""
2902+
view_with_cache = cache_page(3)(cache_control(public=True)(hello_world_view))
2903+
request = self.factory.get("/view/", headers={"Authorization": "token"})
2904+
response = view_with_cache(request, "1")
2905+
self.assertIs(has_vary_header(response, "Authorization"), False)
2906+
28822907
def test_sensitive_cookie_not_cached(self):
28832908
"""
28842909
Django must prevent caching of responses that set a user-specific (and

0 commit comments

Comments
 (0)