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
37 changes: 30 additions & 7 deletions django/contrib/auth/handlers/modwsgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,47 @@
UserModel = auth.get_user_model()


def _get_user(username):
"""
Return the UserModel instance for `username`.
If no matching user exists, or if the user is inactive, return None, in
which case the default password hasher is run to mitigate timing attacks.
"""
try:
user = UserModel._default_manager.get_by_natural_key(username)
except UserModel.DoesNotExist:
user = None
else:
if not user.is_active:
user = None

if user is None:
# Run the default password hasher once to reduce the timing difference
# between existing/active and nonexistent/inactive users (#20760).
UserModel().set_password("")

return user


def check_password(environ, username, password):
"""
Authenticate against Django's auth database.
mod_wsgi docs specify None, True, False as return value depending
on whether the user exists and authenticates.
Return None if the user does not exist, return False if the user exists but
password is not correct, and return True otherwise.
"""
# db connection state is managed similarly to the wsgi handler
# as mod_wsgi may call these functions outside of a request/response cycle
db.reset_queries()
try:
try:
user = UserModel._default_manager.get_by_natural_key(username)
except UserModel.DoesNotExist:
return None
if not user.is_active:
return None
return user.check_password(password)
user = _get_user(username)
if user:
return user.check_password(password)
finally:
db.close_old_connections()

Expand Down
6 changes: 6 additions & 0 deletions django/contrib/gis/db/backends/postgis/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ def check_raster(self, lookup, template_params):

# Look for band indices and inject them if provided.
if lookup.band_lhs is not None and lhs_is_raster:
if not isinstance(lookup.band_lhs, int):
name = lookup.band_lhs.__class__.__name__
raise TypeError(f"Band index must be an integer, but got {name!r}.")
if not self.func:
raise ValueError(
"Band indices are not allowed for this operator, it works on bbox "
Expand All @@ -62,6 +65,9 @@ def check_raster(self, lookup, template_params):
)

if lookup.band_rhs is not None and rhs_is_raster:
if not isinstance(lookup.band_rhs, int):
name = lookup.band_rhs.__class__.__name__
raise TypeError(f"Band index must be an integer, but got {name!r}.")
if not self.func:
raise ValueError(
"Band indices are not allowed for this operator, it works on bbox "
Expand Down
11 changes: 6 additions & 5 deletions django/core/handlers/asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import sys
import tempfile
import traceback
from collections import defaultdict
from contextlib import aclosing, closing

from asgiref.sync import ThreadSensitiveContext, sync_to_async
Expand Down Expand Up @@ -83,6 +84,7 @@ def __init__(self, scope, body_file):
self.META["SERVER_NAME"] = "unknown"
self.META["SERVER_PORT"] = "0"
# Headers go into META.
_headers = defaultdict(list)
for name, value in self.scope.get("headers", []):
name = name.decode("latin1")
if name == "content-length":
Expand All @@ -96,11 +98,10 @@ def __init__(self, scope, body_file):
value = value.decode("latin1")
if corrected_name == "HTTP_COOKIE":
value = value.rstrip("; ")
if "HTTP_COOKIE" in self.META:
value = self.META[corrected_name] + "; " + value
elif corrected_name in self.META:
value = self.META[corrected_name] + "," + value
self.META[corrected_name] = value
_headers[corrected_name].append(value)
if cookie_header := _headers.pop("HTTP_COOKIE", None):
self.META["HTTP_COOKIE"] = "; ".join(cookie_header)
self.META.update({name: ",".join(value) for name, value in _headers.items()})
# Pull out request encoding, if provided.
self._set_content_type_params(self.META)
# Directly assign the body file to be our stream.
Expand Down
2 changes: 1 addition & 1 deletion django/db/models/sql/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ def _order_by_pairs(self):
yield OrderBy(expr, descending=descending), False
continue

if "." in field:
if "." in field and field in self.query.extra_order_by:
# This came in through an extra(order_by=...) addition. Pass it
# on verbatim.
table, col = col.split(".", 1)
Expand Down
28 changes: 19 additions & 9 deletions django/db/models/sql/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,17 @@
__all__ = ["Query", "RawQuery"]

# RemovedInDjango70Warning: When the deprecation ends, replace with:
# Quotation marks ('"`[]), whitespace characters, semicolons, percent signs,
# hashes, or inline SQL comments are forbidden in column aliases.
# FORBIDDEN_ALIAS_PATTERN = _lazy_re_compile(r"['`\"\]\[;\s]|%|#|--|/\*|\*/")
# Quotation marks ('"`[]), whitespace characters, semicolons, hashes, or inline
# SQL comments are forbidden in column aliases.
FORBIDDEN_ALIAS_PATTERN = _lazy_re_compile(r"['`\"\]\[;\s]|#|--|/\*|\*/")
# Quotation marks ('"`[]), whitespace characters, control characters,
# semicolons, percent signs, hashes, or inline SQL comments are
# forbidden in column aliases.
# FORBIDDEN_ALIAS_PATTERN = _lazy_re_compile(
# r"['`\"\]\[;\s\x00-\x1F\x7F-\x9F]|%|#|--|/\*|\*/"
# )
# Quotation marks ('"`[]), whitespace characters, control characters,
# semicolons, hashes, or inline SQL comments are forbidden in column aliases.
FORBIDDEN_ALIAS_PATTERN = _lazy_re_compile(
r"['`\"\]\[;\s\x00-\x1F\x7F-\x9F]|#|--|/\*|\*/"
)

# Inspired from
# https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
Expand Down Expand Up @@ -1226,9 +1231,9 @@ def check_alias(self, alias):
"Column aliases cannot contain whitespace characters, hashes, "
# RemovedInDjango70Warning: When the deprecation ends, replace
# with:
# "quotation marks, semicolons, percent signs, or SQL "
# "comments."
"quotation marks, semicolons, or SQL comments."
# "control characters, quotation marks, semicolons, percent "
# "signs, or SQL comments."
"control characters, quotation marks, semicolons, or SQL comments."
)

def add_annotation(self, annotation, alias, select=True):
Expand Down Expand Up @@ -1715,6 +1720,11 @@ def _add_q(
return target_clause, needed_inner

def add_filtered_relation(self, filtered_relation, alias):
if "." in alias:
raise ValueError(
"FilteredRelation doesn't support aliases with periods "
"(got %r)." % alias
)
self.check_alias(alias)
filtered_relation.alias = alias
relation_lookup_parts, relation_field_parts, _ = self.solve_lookup_type(
Expand Down
9 changes: 5 additions & 4 deletions django/utils/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,11 @@ def handle_starttag(self, tag, attrs):
def handle_endtag(self, tag):
if tag not in self.void_elements:
self.output.append(f"</{tag}>")
try:
self.tags.remove(tag)
except ValueError:
pass
# Remove from the stack only if the tag matches the most recently
# opened tag (LIFO). This avoids O(n) linear scans for unmatched
# end tags if `deque.remove()` would be called.
if self.tags and self.tags[0] == tag:
self.tags.popleft()

def handle_data(self, data):
data, output = self.process(data)
Expand Down
69 changes: 69 additions & 0 deletions docs/releases/4.2.28.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,72 @@ Django 4.2.28 release notes
Django 4.2.28 fixes three security issues with severity "high", two security
issues with severity "moderate", and one security issue with severity "low" in
4.2.27.

CVE-2025-13473: Username enumeration through timing difference in mod_wsgi authentication handler
=================================================================================================

The ``django.contrib.auth.handlers.modwsgi.check_password()`` function for
:doc:`authentication via mod_wsgi</howto/deployment/wsgi/apache-auth>`
allowed remote attackers to enumerate users via a timing attack.

This issue has severity "low" according to the :ref:`Django security policy
<security-disclosure>`.

CVE-2025-14550: Potential denial-of-service vulnerability via repeated headers when using ASGI
==============================================================================================

When receiving duplicates of a single header, ``ASGIRequest`` allowed a remote
attacker to cause a potential denial-of-service via a specifically created
request with multiple duplicate headers. The vulnerability resulted from
repeated string concatenation while combining repeated headers, which
produced super-linear computation resulting in service degradation or outage.

This issue has severity "moderate" according to the :ref:`Django security
policy <security-disclosure>`.

CVE-2026-1207: Potential SQL injection via raster lookups on PostGIS
====================================================================

:ref:`Raster lookups <spatial-lookup-raster>` on GIS fields (only implemented
on PostGIS) were subject to SQL injection if untrusted data was used as a band
index.

As a reminder, all untrusted user input should be validated before use.

This issue has severity "high" according to the :ref:`Django security policy
<security-disclosure>`.

CVE-2026-1285: Potential denial-of-service vulnerability in ``django.utils.text.Truncator`` HTML methods
========================================================================================================

``django.utils.text.Truncator.chars()`` and ``Truncator.words()`` methods (with
``html=True``) and the :tfilter:`truncatechars_html` and
:tfilter:`truncatewords_html` template filters were subject to a potential
denial-of-service attack via certain inputs with a large number of unmatched
HTML end tags, which could cause quadratic time complexity during HTML parsing.

This issue has severity "moderate" according to the :ref:`Django security
policy <security-disclosure>`.

CVE-2026-1287: Potential SQL injection in column aliases via control characters
===============================================================================

:class:`.FilteredRelation` was subject to SQL injection in column aliases via
control characters, using a suitably crafted dictionary, with dictionary
expansion, as the ``**kwargs`` passed to :meth:`.QuerySet.annotate`,
:meth:`~.QuerySet.aggregate`, :meth:`~.QuerySet.extra`,
:meth:`~.QuerySet.values`, :meth:`~.QuerySet.values_list`, and
:meth:`~.QuerySet.alias`.

This issue has severity "high" according to the :ref:`Django security policy
<security-disclosure>`.

CVE-2026-1312: Potential SQL injection via ``QuerySet.order_by`` and ``FilteredRelation``
=========================================================================================

:meth:`.QuerySet.order_by` was subject to SQL injection in column aliases
containing periods when the same alias was, using a suitably crafted
dictionary, with dictionary expansion, used in :class:`.FilteredRelation`.

This issue has severity "high" according to the :ref:`Django security policy
<security-disclosure>`.
69 changes: 69 additions & 0 deletions docs/releases/5.2.11.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,72 @@ Django 5.2.11 release notes
Django 5.2.11 fixes three security issues with severity "high", two security
issues with severity "moderate", and one security issue with severity "low" in
5.2.10.

CVE-2025-13473: Username enumeration through timing difference in mod_wsgi authentication handler
=================================================================================================

The ``django.contrib.auth.handlers.modwsgi.check_password()`` function for
:doc:`authentication via mod_wsgi</howto/deployment/wsgi/apache-auth>`
allowed remote attackers to enumerate users via a timing attack.

This issue has severity "low" according to the :ref:`Django security policy
<security-disclosure>`.

CVE-2025-14550: Potential denial-of-service vulnerability via repeated headers when using ASGI
==============================================================================================

When receiving duplicates of a single header, ``ASGIRequest`` allowed a remote
attacker to cause a potential denial-of-service via a specifically created
request with multiple duplicate headers. The vulnerability resulted from
repeated string concatenation while combining repeated headers, which
produced super-linear computation resulting in service degradation or outage.

This issue has severity "moderate" according to the :ref:`Django security
policy <security-disclosure>`.

CVE-2026-1207: Potential SQL injection via raster lookups on PostGIS
====================================================================

:ref:`Raster lookups <spatial-lookup-raster>` on GIS fields (only implemented
on PostGIS) were subject to SQL injection if untrusted data was used as a band
index.

As a reminder, all untrusted user input should be validated before use.

This issue has severity "high" according to the :ref:`Django security policy
<security-disclosure>`.

CVE-2026-1285: Potential denial-of-service vulnerability in ``django.utils.text.Truncator`` HTML methods
========================================================================================================

``django.utils.text.Truncator.chars()`` and ``Truncator.words()`` methods (with
``html=True``) and the :tfilter:`truncatechars_html` and
:tfilter:`truncatewords_html` template filters were subject to a potential
denial-of-service attack via certain inputs with a large number of unmatched
HTML end tags, which could cause quadratic time complexity during HTML parsing.

This issue has severity "moderate" according to the :ref:`Django security
policy <security-disclosure>`.

CVE-2026-1287: Potential SQL injection in column aliases via control characters
===============================================================================

:class:`.FilteredRelation` was subject to SQL injection in column aliases via
control characters, using a suitably crafted dictionary, with dictionary
expansion, as the ``**kwargs`` passed to :meth:`.QuerySet.annotate`,
:meth:`~.QuerySet.aggregate`, :meth:`~.QuerySet.extra`,
:meth:`~.QuerySet.values`, :meth:`~.QuerySet.values_list`, and
:meth:`~.QuerySet.alias`.

This issue has severity "high" according to the :ref:`Django security policy
<security-disclosure>`.

CVE-2026-1312: Potential SQL injection via ``QuerySet.order_by`` and ``FilteredRelation``
=========================================================================================

:meth:`.QuerySet.order_by` was subject to SQL injection in column aliases
containing periods when the same alias was, using a suitably crafted
dictionary, with dictionary expansion, used in :class:`.FilteredRelation`.

This issue has severity "high" according to the :ref:`Django security policy
<security-disclosure>`.
Loading