Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions django/db/models/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
# The maximum number of items to display in a QuerySet.__repr__
REPR_OUTPUT_SIZE = 20

PROHIBITED_FILTER_KWARGS = frozenset(["_connector", "_negated"])


class BaseIterable:
def __init__(
Expand Down Expand Up @@ -1645,6 +1647,9 @@ def _filter_or_exclude(self, negate, args, kwargs):
return clone

def _filter_or_exclude_inplace(self, negate, args, kwargs):
if invalid_kwargs := PROHIBITED_FILTER_KWARGS.intersection(kwargs):
invalid_kwargs_str = ", ".join(f"'{k}'" for k in sorted(invalid_kwargs))
raise TypeError(f"The following kwargs are invalid: {invalid_kwargs_str}")
if negate:
self._query.add_q(~Q(*args, **kwargs))
else:
Expand Down
4 changes: 4 additions & 0 deletions django/db/models/query_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,12 @@ class Q(tree.Node):
XOR = "XOR"
default = AND
conditional = True
connectors = (None, AND, OR, XOR)

def __init__(self, *args, _connector=None, _negated=False, **kwargs):
if _connector not in self.connectors:
connector_reprs = ", ".join(f"{conn!r}" for conn in self.connectors[1:])
raise ValueError(f"_connector must be one of {connector_reprs}, or None.")
super().__init__(
children=[*args, *sorted(kwargs.items())],
connector=_connector,
Expand Down
9 changes: 7 additions & 2 deletions django/http/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from django.utils.datastructures import CaseInsensitiveMapping
from django.utils.encoding import iri_to_uri
from django.utils.functional import cached_property
from django.utils.http import content_disposition_header, http_date
from django.utils.http import MAX_URL_LENGTH, content_disposition_header, http_date
from django.utils.regex_helper import _lazy_re_compile

_charset_from_content_type_re = _lazy_re_compile(
Expand Down Expand Up @@ -631,7 +631,12 @@ class HttpResponseRedirectBase(HttpResponse):
def __init__(self, redirect_to, preserve_request=False, *args, **kwargs):
super().__init__(*args, **kwargs)
self["Location"] = iri_to_uri(redirect_to)
parsed = urlsplit(str(redirect_to))
redirect_to_str = str(redirect_to)
if len(redirect_to_str) > MAX_URL_LENGTH:
raise DisallowedRedirect(
f"Unsafe redirect exceeding {MAX_URL_LENGTH} characters"
)
parsed = urlsplit(redirect_to_str)
if preserve_request:
self.status_code = self.status_code_preserve_request
if parsed.scheme and parsed.scheme not in self.allowed_schemes:
Expand Down
17 changes: 16 additions & 1 deletion docs/releases/4.2.26.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,19 @@ Django 4.2.26 release notes
Django 4.2.26 fixes one security issue with severity "high" and one security
issue with severity "moderate" in 4.2.25.

...
CVE-2025-64458: Potential denial-of-service vulnerability in ``HttpResponseRedirect`` and ``HttpResponsePermanentRedirect`` on Windows
======================================================================================================================================

Python's :func:`NFKC normalization <python:unicodedata.normalize>` is slow on
Windows. As a consequence, :class:`~django.http.HttpResponseRedirect`,
:class:`~django.http.HttpResponsePermanentRedirect`, and the shortcut
:func:`redirect() <django.shortcuts.redirect>` were subject to a potential
denial-of-service attack via certain inputs with a very large number of Unicode
characters (follow up to :cve:`2025-27556`).

CVE-2025-64459: Potential SQL injection via ``_connector`` keyword argument
===========================================================================

:meth:`.QuerySet.filter`, :meth:`~.QuerySet.exclude`, :meth:`~.QuerySet.get`,
and :class:`~.Q` were subject to SQL injection using a suitably crafted
dictionary, with dictionary expansion, as the ``_connector`` argument.
17 changes: 16 additions & 1 deletion docs/releases/5.1.14.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,19 @@ Django 5.1.14 release notes
Django 5.1.14 fixes one security issue with severity "high" and one security
issue with severity "moderate" in 5.1.13.

...
CVE-2025-64458: Potential denial-of-service vulnerability in ``HttpResponseRedirect`` and ``HttpResponsePermanentRedirect`` on Windows
======================================================================================================================================

Python's :func:`NFKC normalization <python:unicodedata.normalize>` is slow on
Windows. As a consequence, :class:`~django.http.HttpResponseRedirect`,
:class:`~django.http.HttpResponsePermanentRedirect`, and the shortcut
:func:`redirect() <django.shortcuts.redirect>` were subject to a potential
denial-of-service attack via certain inputs with a very large number of Unicode
characters (follow up to :cve:`2025-27556`).

CVE-2025-64459: Potential SQL injection via ``_connector`` keyword argument
===========================================================================

:meth:`.QuerySet.filter`, :meth:`~.QuerySet.exclude`, :meth:`~.QuerySet.get`,
and :class:`~.Q` were subject to SQL injection using a suitably crafted
dictionary, with dictionary expansion, as the ``_connector`` argument.
17 changes: 17 additions & 0 deletions docs/releases/5.2.8.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,23 @@ Django 5.2.8 fixes one security issue with severity "high", one security issue
with severity "moderate", and several bugs in 5.2.7. It also adds compatibility
with Python 3.14.

CVE-2025-64458: Potential denial-of-service vulnerability in ``HttpResponseRedirect`` and ``HttpResponsePermanentRedirect`` on Windows
======================================================================================================================================

Python's :func:`NFKC normalization <python:unicodedata.normalize>` is slow on
Windows. As a consequence, :class:`~django.http.HttpResponseRedirect`,
:class:`~django.http.HttpResponsePermanentRedirect`, and the shortcut
:func:`redirect() <django.shortcuts.redirect>` were subject to a potential
denial-of-service attack via certain inputs with a very large number of Unicode
characters (follow up to :cve:`2025-27556`).

CVE-2025-64459: Potential SQL injection via ``_connector`` keyword argument
===========================================================================

:meth:`.QuerySet.filter`, :meth:`~.QuerySet.exclude`, :meth:`~.QuerySet.get`,
and :class:`~.Q` were subject to SQL injection using a suitably crafted
dictionary, with dictionary expansion, as the ``_connector`` argument.

Bugfixes
========

Expand Down
12 changes: 12 additions & 0 deletions docs/releases/5.2.9.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
==========================
Django 5.2.9 release notes
==========================

*Expected December 2, 2025*

Django 5.2.9 fixes several bugs in 5.2.8.

Bugfixes
========

* ...
1 change: 1 addition & 0 deletions docs/releases/index.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ versions of the documentation contain the release notes for any later releases.
.. toctree::
:maxdepth: 1

5.2.9
5.2.8
5.2.7
5.2.6
Expand Down
24 changes: 24 additions & 0 deletions docs/releases/security.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,30 @@ Issues under Django's security process
All security issues have been handled under versions of Django's security
process. These are listed below.

November 5, 2025 - :cve:`2025-64458`
------------------------------------

Potential denial-of-service vulnerability in ``HttpResponseRedirect`` and
``HttpResponsePermanentRedirect`` on Windows. `Full description
<https://www.djangoproject.com/weblog/2025/nov/05/security-releases/>`__

* Django 6.0 :commit:`(patch) <6e13348436fccf8f22982921d6a3a3e65c956a9f>`
* Django 5.2 :commit:`(patch) <4f5d904b63751dea9ffc3b0e046404a7fa5881ac>`
* Django 5.1 :commit:`(patch) <3790593781d26168e7306b5b2f8ea0309de16242>`
* Django 4.2 :commit:`(patch) <770eea38d7a0e9ba9455140b5a9a9e33618226a7>`

November 5, 2025 - :cve:`2025-64459`
------------------------------------

Potential SQL injection via ``_connector`` keyword argument in ``QuerySet`` and
``Q`` objects. `Full description
<https://www.djangoproject.com/weblog/2025/nov/05/security-releases/>`__

* Django 6.0 :commit:`(patch) <06dd38324ac3d60d83d9f3adabf0dcdf423d2a85>`
* Django 5.2 :commit:`(patch) <6703f364d767e949c5b0e4016433ef75063b4f9b>`
* Django 5.1 :commit:`(patch) <72d2c87431f2ae0431d65d0ec792047f078c8241>`
* Django 4.2 :commit:`(patch) <59ae82e67053d281ff4562a24bbba21299f0a7d4>`

October 1, 2025 - :cve:`2025-59681`
-----------------------------------

Expand Down
2 changes: 2 additions & 0 deletions tests/httpwrappers/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
)
from django.test import SimpleTestCase
from django.utils.functional import lazystr
from django.utils.http import MAX_URL_LENGTH


class QueryDictTests(SimpleTestCase):
Expand Down Expand Up @@ -490,6 +491,7 @@ def test_unsafe_redirect(self):
'data:text/html,<script>window.alert("xss")</script>',
"mailto:test@example.com",
"file:///etc/passwd",
"é" * (MAX_URL_LENGTH + 1),
]
for url in bad_urls:
with self.assertRaises(DisallowedRedirect):
Expand Down
5 changes: 5 additions & 0 deletions tests/queries/test_q.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,11 @@ def test_create_helper(self):
Q(*items, _connector=connector),
)

def test_connector_validation(self):
msg = f"_connector must be one of {Q.AND!r}, {Q.OR!r}, {Q.XOR!r}, or None."
with self.assertRaisesMessage(ValueError, msg):
Q(_connector="evil")

def test_referenced_base_fields(self):
# Make sure Q.referenced_base_fields retrieves all base fields from
# both filters and F expressions.
Expand Down
8 changes: 8 additions & 0 deletions tests/queries/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4516,6 +4516,14 @@ def test_invalid_values(self):
Annotation.objects.filter(tag__in=[123, "abc"])


class TestInvalidFilterArguments(TestCase):
def test_filter_rejects_invalid_arguments(self):
school = School.objects.create()
msg = "The following kwargs are invalid: '_connector', '_negated'"
with self.assertRaisesMessage(TypeError, msg):
School.objects.filter(pk=school.pk, _negated=True, _connector="evil")


class TestTicket24605(TestCase):
def test_ticket_24605(self):
"""
Expand Down