From 6e365f8d01f2ba0bbd90968d76a42600fb8bc4b1 Mon Sep 17 00:00:00 2001 From: Natalia <124304+nessita@users.noreply.github.com> Date: Thu, 11 Jun 2026 16:23:37 -0300 Subject: [PATCH 1/5] Fixed CVE-2026-48588 -- Prevented caching of responses that set cookies and vary on Cookie. `UpdateCacheMiddleware` skipped caching `Set-Cookie` responses that vary on `Cookie` only when the request had no cookies at all. A request carrying an unrelated cookie bypassed the guard, allowing a newly-issued session cookie to be stored in Django's shared cache. The guard now applies whenever a response both sets a cookie and varies on Cookie, regardless of what cookies the incoming request carried. Thanks Chris Whyland for the report, Jake Howard for initial triage, and Jacob Walls for reviews. --- django/middleware/cache.py | 8 ++------ docs/releases/5.2.16.txt | 14 ++++++++++++++ docs/releases/6.0.7.txt | 14 ++++++++++++++ tests/cache/tests.py | 38 +++++++++++++++++++++++++++++++------- 4 files changed, 61 insertions(+), 13 deletions(-) diff --git a/django/middleware/cache.py b/django/middleware/cache.py index f4feab96eb19..21bdad33f408 100644 --- a/django/middleware/cache.py +++ b/django/middleware/cache.py @@ -95,12 +95,8 @@ def process_response(self, request, response): return response # Don't cache responses that set a user-specific (and maybe security - # sensitive) cookie in response to a cookie-less request. - if ( - not request.COOKIES - and response.cookies - and has_vary_header(response, "Cookie") - ): + # sensitive) cookie while varying on Cookie. + if response.cookies and has_vary_header(response, "Cookie"): return response # Don't cache responses when the Cache-Control header is set to diff --git a/docs/releases/5.2.16.txt b/docs/releases/5.2.16.txt index 26449e18ed41..86b0c8d4ba6b 100644 --- a/docs/releases/5.2.16.txt +++ b/docs/releases/5.2.16.txt @@ -5,3 +5,17 @@ Django 5.2.16 release notes *July 7, 2026* Django 5.2.16 fixes three security issues with severity "low" in 5.2.15. + +CVE-2026-48588: Potential exposure of private data via cached ``Set-Cookie`` response +===================================================================================== + +:class:`~django.middleware.cache.UpdateCacheMiddleware` and +:func:`~django.views.decorators.cache.cache_page` avoided caching responses +that set a cookie while varying on ``Cookie`` only when the incoming request +contained no cookies at all. When the request already carried an unrelated +cookie (such as a language or theme preference cookie), the protection did not +apply, allowing a response that sets a session or other sensitive cookie to be +stored in Django's shared cache. + +This issue has severity "low" according to the :ref:`Django security policy +`. diff --git a/docs/releases/6.0.7.txt b/docs/releases/6.0.7.txt index 3aebb9a5445b..0990bef39d90 100644 --- a/docs/releases/6.0.7.txt +++ b/docs/releases/6.0.7.txt @@ -7,6 +7,20 @@ Django 6.0.7 release notes Django 6.0.7 fixes three security issues with severity "low" and one bug in 6.0.6. +CVE-2026-48588: Potential exposure of private data via cached ``Set-Cookie`` response +===================================================================================== + +:class:`~django.middleware.cache.UpdateCacheMiddleware` and +:func:`~django.views.decorators.cache.cache_page` avoided caching responses +that set a cookie while varying on ``Cookie`` only when the incoming request +contained no cookies at all. When the request already carried an unrelated +cookie (such as a language or theme preference cookie), the protection did not +apply, allowing a response that sets a session or other sensitive cookie to be +stored in Django's shared cache. + +This issue has severity "low" according to the :ref:`Django security policy +`. + Bugfixes ======== diff --git a/tests/cache/tests.py b/tests/cache/tests.py index 4b5d18fec114..96385fd084de 100644 --- a/tests/cache/tests.py +++ b/tests/cache/tests.py @@ -3181,18 +3181,42 @@ def test_authorization_header_exception_superstring(self): def test_sensitive_cookie_not_cached(self): """ - Django must prevent caching of responses that set a user-specific (and - maybe security sensitive) cookie in response to a cookie-less request. + Responses that set a new cookie not present in the request are not + cached, regardless of whether the request already had other cookies. """ - request = self.factory.get("/view/") - csrf_middleware = CsrfViewMiddleware(csrf_view) - csrf_middleware.process_view(request, csrf_view, (), {}) - cache_middleware = CacheMiddleware(csrf_middleware) + for headers in ({}, {"HTTP_COOKIE": "unrelated=value"}): + with self.subTest(headers=headers): + request = self.factory.get("/view/", **headers) + csrf_middleware = CsrfViewMiddleware(csrf_view) + csrf_middleware.process_view(request, csrf_view, (), {}) + cache_middleware = CacheMiddleware(csrf_middleware) + + self.assertIsNone(cache_middleware.process_request(request)) + cache_middleware(request) + + # Inserting a CSRF cookie prevented caching. + self.assertIsNone(cache_middleware.process_request(request)) + + def test_refreshed_cookie_not_cached(self): + """ + A response that updates the value of a cookie already present in the + request is not cached. Caching it would allow a stale cookie value to + be served to a different client via a Vary: Cookie cache hit. + """ + + def refreshing_cookie_view(request): + response = HttpResponse("content") + response.set_cookie("session", "new_value") + patch_vary_headers(response, ("Cookie",)) + return response + + request = self.factory.get("/view/", HTTP_COOKIE="session=old_value") + cache_middleware = CacheMiddleware(refreshing_cookie_view) self.assertIsNone(cache_middleware.process_request(request)) cache_middleware(request) - # Inserting a CSRF cookie in a cookie-less request prevented caching. + # The response refreshed an existing cookie so it must not be cached. self.assertIsNone(cache_middleware.process_request(request)) def test_304_response_has_http_caching_headers_but_not_cached(self): From 6ca2bbe2efce21010eff48f1f36a3f621d698ed8 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Tue, 16 Jun 2026 15:53:48 -0400 Subject: [PATCH 2/5] Fixed CVE-2026-53877 -- Prevented heap buffer over-read when creating GDALRaster from bytes. Previously, `sys.getsizeof()` included the size of the `PyBytesObject` wrapper which is bigger. `len(bytes_object)` is the accurate size. Thanks Bence Nagy for the report, and Simon Charette for reviews. --- django/contrib/gis/gdal/raster/source.py | 3 +-- docs/releases/5.2.16.txt | 14 ++++++++++++++ docs/releases/6.0.7.txt | 14 ++++++++++++++ tests/gis_tests/gdal_tests/test_raster.py | 6 ++++++ 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/django/contrib/gis/gdal/raster/source.py b/django/contrib/gis/gdal/raster/source.py index 93c590097086..4f0eb875d35e 100644 --- a/django/contrib/gis/gdal/raster/source.py +++ b/django/contrib/gis/gdal/raster/source.py @@ -1,6 +1,5 @@ import json import os -import sys import uuid from ctypes import ( addressof, @@ -103,7 +102,7 @@ def __init__(self, ds_input, write=False): # Create a new raster in write mode. self._write = 1 # Get size of buffer. - size = sys.getsizeof(ds_input) + size = len(ds_input) # Pass data to ctypes, keeping a reference to the ctypes object so # that the vsimem file remains available until the GDALRaster is # deleted. diff --git a/docs/releases/5.2.16.txt b/docs/releases/5.2.16.txt index 86b0c8d4ba6b..9aa1b1a32f9b 100644 --- a/docs/releases/5.2.16.txt +++ b/docs/releases/5.2.16.txt @@ -19,3 +19,17 @@ stored in Django's shared cache. This issue has severity "low" according to the :ref:`Django security policy `. + +CVE-2026-53877: Heap buffer over-read in ``GDALRaster`` +======================================================= + +When :class:`~django.contrib.gis.gdal.GDALRaster` was instantiated with a bytes +object representing a raster file, the +:attr:`~django.contrib.gis.gdal.GDALRaster.vsi_buffer` property could over-read +the allocated buffer by approximately 32 bytes. This could result in +information disclosure of adjacent heap memory or, in rare cases, a +segmentation fault. Only rasters stored in GDAL's virtual filesystem were +affected. + +This issue has severity "low" according to the :ref:`Django security policy +`. diff --git a/docs/releases/6.0.7.txt b/docs/releases/6.0.7.txt index 0990bef39d90..374810a8a46b 100644 --- a/docs/releases/6.0.7.txt +++ b/docs/releases/6.0.7.txt @@ -21,6 +21,20 @@ stored in Django's shared cache. This issue has severity "low" according to the :ref:`Django security policy `. +CVE-2026-53877: Heap buffer over-read in ``GDALRaster`` +======================================================= + +When :class:`~django.contrib.gis.gdal.GDALRaster` was instantiated with a bytes +object representing a raster file, the +:attr:`~django.contrib.gis.gdal.GDALRaster.vsi_buffer` property could over-read +the allocated buffer by approximately 32 bytes. This could result in +information disclosure of adjacent heap memory or, in rare cases, a +segmentation fault. Only rasters stored in GDAL's virtual filesystem were +affected. + +This issue has severity "low" according to the :ref:`Django security policy +`. + Bugfixes ======== diff --git a/tests/gis_tests/gdal_tests/test_raster.py b/tests/gis_tests/gdal_tests/test_raster.py index 2c2b77d5cd5a..d801317d5234 100644 --- a/tests/gis_tests/gdal_tests/test_raster.py +++ b/tests/gis_tests/gdal_tests/test_raster.py @@ -265,6 +265,12 @@ def test_vsi_buffer_property(self): # The vsi buffer is None for rasters that are not vsi based. self.assertIsNone(self.rs.vsi_buffer) + def test_vsi_buffer_length(self): + with open(self.rs_path, "rb") as rst_file: + rst_bytes = rst_file.read() + vsimem = GDALRaster(rst_bytes) + self.assertEqual(len(vsimem.vsi_buffer), len(rst_bytes)) + def test_vsi_vsizip_filesystem(self): rst_zipfile = NamedTemporaryFile(suffix=".zip") with zipfile.ZipFile(rst_zipfile, mode="w") as zf: From 3a720d0d8bf2529253b98968f10ca73daf6d693c Mon Sep 17 00:00:00 2001 From: Natalia <124304+nessita@users.noreply.github.com> Date: Thu, 11 Jun 2026 15:17:31 -0300 Subject: [PATCH 3/5] Fixed CVE-2026-53878 -- Prevented newlines from being accepted in DomainNameValidator. Thanks Bence Nagy for the report, and Jake Howard for reviews. --- django/core/validators.py | 28 ++++++++++++++-------------- docs/releases/5.2.16.txt | 14 ++++++++++++++ docs/releases/6.0.7.txt | 14 ++++++++++++++ tests/validators/tests.py | 4 ++++ 4 files changed, 46 insertions(+), 14 deletions(-) diff --git a/django/core/validators.py b/django/core/validators.py index 534acdd904e0..673458aacfca 100644 --- a/django/core/validators.py +++ b/django/core/validators.py @@ -99,20 +99,20 @@ class DomainNameValidator(RegexValidator): def __init__(self, **kwargs): self.accept_idna = kwargs.pop("accept_idna", True) - if self.accept_idna: - self.regex = _lazy_re_compile( - r"^" + self.hostname_re + self.domain_re + self.tld_re + r"$", - re.IGNORECASE, - ) - else: - self.regex = _lazy_re_compile( - r"^" - + self.ascii_only_hostname_re - + self.ascii_only_domain_re - + self.ascii_only_tld_re - + r"$", - re.IGNORECASE, - ) + regex_parts = [ + "^", + *( + (self.hostname_re, self.domain_re, self.tld_re) + if self.accept_idna + else ( + self.ascii_only_hostname_re, + self.ascii_only_domain_re, + self.ascii_only_tld_re, + ) + ), + r"\Z", + ] + self.regex = _lazy_re_compile("".join(regex_parts), re.IGNORECASE) super().__init__(**kwargs) def __call__(self, value): diff --git a/docs/releases/5.2.16.txt b/docs/releases/5.2.16.txt index 9aa1b1a32f9b..78efa1672225 100644 --- a/docs/releases/5.2.16.txt +++ b/docs/releases/5.2.16.txt @@ -33,3 +33,17 @@ affected. This issue has severity "low" according to the :ref:`Django security policy `. + +CVE-2026-53878: Header injection possibility since ``DomainNameValidator`` accepted newlines in input +===================================================================================================== + +:class:`~django.core.validators.DomainNameValidator` accepted newlines in +domain names. If such values were included in HTTP responses, header injection +attacks were possible. Django itself wasn't vulnerable because +:class:`~django.http.HttpResponse` prohibits newlines in HTTP headers. + +The vulnerability only affected uses of ``DomainNameValidator`` outside Django +form fields, as ``CharField`` strips newlines by default. + +This issue has severity "low" according to the :ref:`Django security policy +`. diff --git a/docs/releases/6.0.7.txt b/docs/releases/6.0.7.txt index 374810a8a46b..584f67917c38 100644 --- a/docs/releases/6.0.7.txt +++ b/docs/releases/6.0.7.txt @@ -35,6 +35,20 @@ affected. This issue has severity "low" according to the :ref:`Django security policy `. +CVE-2026-53878: Header injection possibility since ``DomainNameValidator`` accepted newlines in input +===================================================================================================== + +:class:`~django.core.validators.DomainNameValidator` accepted newlines in +domain names. If such values were included in HTTP responses, header injection +attacks were possible. Django itself wasn't vulnerable because +:class:`~django.http.HttpResponse` prohibits newlines in HTTP headers. + +The vulnerability only affected uses of ``DomainNameValidator`` outside Django +form fields, as ``CharField`` strips newlines by default. + +This issue has severity "low" according to the :ref:`Django security policy +`. + Bugfixes ======== diff --git a/tests/validators/tests.py b/tests/validators/tests.py index 49f4b6acbb80..e9ef60174aad 100644 --- a/tests/validators/tests.py +++ b/tests/validators/tests.py @@ -665,6 +665,8 @@ (validate_domain_name, "dashinpunytld.xn---c", None), (validate_domain_name, "python..org", ValidationError), (validate_domain_name, "python-.org", ValidationError), + (validate_domain_name, "example.com\n", ValidationError), + (validate_domain_name, "example.com\r\n", ValidationError), (validate_domain_name, "too-long-name." * 20 + "com", ValidationError), (validate_domain_name, "stupid-name试", ValidationError), (validate_domain_name, "255.0.0.0", ValidationError), @@ -677,6 +679,8 @@ ValidationError, ), (DomainNameValidator(accept_idna=False), "ıçğü.com", ValidationError), + (DomainNameValidator(accept_idna=False), "example.com\n", ValidationError), + (DomainNameValidator(accept_idna=False), "example.com\r\n", ValidationError), (DomainNameValidator(accept_idna=False), "not-domain-name", ValidationError), ( DomainNameValidator(accept_idna=False), From 2f3acc05eb530fbeb9418a48ab482a2a72d60b59 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Tue, 7 Jul 2026 09:56:33 -0400 Subject: [PATCH 4/5] Added stub release notes for 6.0.8. --- docs/releases/6.0.8.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/6.0.8.txt diff --git a/docs/releases/6.0.8.txt b/docs/releases/6.0.8.txt new file mode 100644 index 000000000000..4a6d167e1291 --- /dev/null +++ b/docs/releases/6.0.8.txt @@ -0,0 +1,12 @@ +========================== +Django 6.0.8 release notes +========================== + +*Expected August 4, 2026* + +Django 6.0.8 fixes several bugs in 6.0.7. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 95789a3fa624..484f40f0e2d9 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -39,6 +39,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 6.0.8 6.0.7 6.0.6 6.0.5 From c93a9e3c8817f0128ed3e1b9f8e46da4cd86b699 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Tue, 7 Jul 2026 09:59:47 -0400 Subject: [PATCH 5/5] Added CVE-2026-48588, CVE-2026-53877, and CVE-2026-53878 to security archive. --- docs/releases/security.txt | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/releases/security.txt b/docs/releases/security.txt index 243895497822..50a87f6fc84d 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -36,6 +36,40 @@ Issues under Django's security process All security issues have been handled under versions of Django's security process. These are listed below. +July 7, 2026 - :cve:`2026-48588` +-------------------------------- + +Potential exposure of private data via cached ``Set-Cookie`` response. +`Full description +`__ + +* Django 6.1 :commit:`(patch) ` +* Django 6.0 :commit:`(patch) <64f9a2b2283fde3ec69fb0dfe441cf0f6f411ba3>` +* Django 5.2 :commit:`(patch) <721685aa7799cc9327bd202cd1f70bd012ca95a7>` + +July 7, 2026 - :cve:`2026-53877` +-------------------------------- + +Heap buffer over-read in ``GDALRaster``. +`Full description +`__ + +* Django 6.1 :commit:`(patch) ` +* Django 6.0 :commit:`(patch) <38dfbd27d7d4f4e6eaa087d7a90f2613fbf55b3a>` +* Django 5.2 :commit:`(patch) <6c66eb8cec52b303af85c2c6e4dd00aa37654dbc>` + +July 7, 2026 - :cve:`2026-53878` +-------------------------------- + +Header injection possibility since ``DomainNameValidator`` accepted newlines in +input. +`Full description +`__ + +* Django 6.1 :commit:`(patch) ` +* Django 6.0 :commit:`(patch) ` +* Django 5.2 :commit:`(patch) ` + June 3, 2026 - :cve:`2026-6873` -------------------------------