티스토리 수익 글 보기

티스토리 수익 글 보기

[5.2.x] Fixed CVE-2026-48587 — Ignored whitespace padding when check… · django/django@9b62b0a · GitHub
Skip to content

Commit 9b62b0a

Browse files
RealOrangeOnenessita
authored andcommitted
[5.2.x] Fixed CVE-2026-48587 — Ignored whitespace padding when checking Vary header values.
Thanks to Navid Rezazadeh for the report and Jacob Walls for review. Backport of 42aa0b3 from main.
1 parent 050a3dc commit 9b62b0a

3 files changed

Lines changed: 49 additions & 6 deletions

File tree

django/utils/cache.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,8 +331,8 @@ def has_vary_header(response, header_query):
331331
if not response.has_header("Vary"):
332332
return False
333333
vary_headers = cc_delim_re.split(response.headers["Vary"])
334-
existing_headers = {header.lower() for header in vary_headers}
335-
return header_query.lower() in existing_headers
334+
existing_headers = {header.lower().strip() for header in vary_headers}
335+
return header_query.lower().strip() in existing_headers
336336

337337

338338
def _i18n_cache_key_suffix(request, cache_key):

docs/releases/5.2.15.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,16 @@ cache keys, responses to these requests will now :ref:`vary on
6464

6565
This issue has severity "low" according to the :ref:`Django security policy
6666
<severity-levels>`.
67+
68+
CVE-2026-48587: Potential exposure of private data via whitespace padding in ``Vary`` header
69+
============================================================================================
70+
71+
:class:`~django.middleware.cache.UpdateCacheMiddleware` incorrectly cached
72+
responses whose ``Vary`` header values contained leading or trailing
73+
whitespace. Because ``has_vary_header()`` failed to strip that, a ``Vary: *``
74+
header value with surrounding whitespace was not recognized as containing the
75+
wildcard, causing it to be stored and potentially served from the cache when it
76+
should not have been.
77+
78+
This issue has severity "low" according to the :ref:`Django security policy
79+
<severity-levels>`.

tests/cache/tests.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import io
55
import os
66
import pickle
7-
import re
87
import shutil
98
import sys
109
import tempfile
@@ -56,6 +55,7 @@
5655
from django.test.utils import CaptureQueriesContext
5756
from django.utils import timezone, translation
5857
from django.utils.cache import (
58+
cc_delim_re,
5959
get_cache_key,
6060
has_vary_header,
6161
learn_cache_key,
@@ -2198,8 +2198,6 @@ def test_patch_cache_control(self):
21982198
),
21992199
)
22002200

2201-
cc_delim_re = re.compile(r"\s*,\s*")
2202-
22032201
for initial_cc, newheaders, expected_cc in tests:
22042202
with self.subTest(initial_cc=initial_cc, newheaders=newheaders):
22052203
response = HttpResponse()
@@ -2209,6 +2207,31 @@ def test_patch_cache_control(self):
22092207
parts = set(cc_delim_re.split(response.headers["Cache-Control"]))
22102208
self.assertEqual(parts, expected_cc)
22112209

2210+
def test_has_vary_header(self):
2211+
tests = [
2212+
("*", "*", True),
2213+
("Cookie, *", "*", True),
2214+
("Cookie,*", "*", True),
2215+
("Cookie , *", "*", True),
2216+
# Surronding whitespace on values must be stripped independently of
2217+
# the comma delimiter.
2218+
("* ", "*", True),
2219+
(" *", "*", True),
2220+
("Cookie, * ", "*", True),
2221+
(" Cookie", "Cookie", True),
2222+
("Cookie", "*", False),
2223+
("*", "Cookie", False),
2224+
("cookie", "Cookie", True),
2225+
("Cookie", "cookie", True),
2226+
]
2227+
2228+
for header_value, header_query, has_match in tests:
2229+
with self.subTest(header_value=header_value, header_query=header_query):
2230+
response = HttpResponse()
2231+
response.headers["Vary"] = header_value
2232+
2233+
self.assertIs(has_vary_header(response, header_query), has_match)
2234+
22122235

22132236
@override_settings(
22142237
CACHES={
@@ -2512,9 +2535,15 @@ def hello_world_view_patch_vary_headers_asterisk(request, value):
25122535
return response
25132536

25142537

2538+
def hello_world_view_patch_vary_headers_asterisk_space(request, value):
2539+
response = HttpResponse("Hello World %s" % value)
2540+
patch_vary_headers(response, (" * ",))
2541+
return response
2542+
2543+
25152544
def hello_world_view_vary_headers_includes_asterisk(request, value):
25162545
response = HttpResponse("Hello World %s" % value)
2517-
response["Vary"] = "Cookie, *, Pony"
2546+
response["Vary"] = "Cookie, * , Pony"
25182547
return response
25192548

25202549

@@ -2758,6 +2787,7 @@ def view(request, value):
27582787
def test_vary_asterisk_not_cached(self):
27592788
views_with_cache = (
27602789
cache_page(3)(hello_world_view_patch_vary_headers_asterisk),
2790+
cache_page(3)(hello_world_view_patch_vary_headers_asterisk_space),
27612791
cache_page(3)(hello_world_view_vary_headers_includes_asterisk),
27622792
)
27632793
for view in views_with_cache:

0 commit comments

Comments
 (0)