File CVE-2026-1312.patch of Package python-Django.19338
From 8298d0c622cad26933b229d29bf1d2c66a2c12e7 Mon Sep 17 00:00:00 2001
From: Jacob Walls <jacobtylerwalls@gmail.com>
Date: Wed, 21 Jan 2026 17:53:52 -0500
Subject: [PATCH 6/7] [4.2.x] Fixed CVE-2026-1312 -- Protected order_by() from
SQL injection via aliases with periods.
Before, `order_by()` treated a period in a field name as a sign that it
was requested via `.extra(order_by=...)` and thus should be passed
through as raw table and column names, even if `extra()` was not used.
Since periods are permitted in aliases, this meant user-controlled
aliases could force the `order_by()` clause to resolve to a raw table
and column pair instead of the actual target field for the alias.
In practice, only `FilteredRelation` was affected, as the other
expressions we tested, e.g. `F`, aggressively optimize away the ordering
expressions into ordinal positions, e.g. ORDER BY 2, instead of ORDER BY
"table".column.
Thanks Solomon Kebede for the report, and Simon Charette and Jake Howard
for reviews.
Backport of 6df131d8c584d308332c9d19fe09221f03dabcc6 from main.
---
django/db/models/sql/compiler.py | 2 +-
docs/releases/4.2.28.txt | 10 ++++++++++
tests/ordering/tests.py | 25 +++++++++++++++++++++++++
3 files changed, 36 insertions(+), 1 deletion(-)
Index: Django-2.2.28/django/db/models/sql/compiler.py
===================================================================
--- Django-2.2.28.orig/django/db/models/sql/compiler.py
+++ Django-2.2.28/django/db/models/sql/compiler.py
@@ -312,7 +312,7 @@ class SQLCompiler:
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)
Index: Django-2.2.28/tests/ordering/tests.py
===================================================================
--- Django-2.2.28.orig/tests/ordering/tests.py
+++ Django-2.2.28/tests/ordering/tests.py
@@ -1,8 +1,9 @@
from datetime import datetime
from operator import attrgetter
-from django.db.models import Count, DateTimeField, F, Max, OuterRef, Subquery
+from django.db.models import Count, DateTimeField, F, FilteredRelation, Max, OuterRef, Subquery
from django.db.models.functions import Upper
+from django.db.utils import DatabaseError
from django.test import TestCase
from django.utils.deprecation import RemovedInDjango31Warning
@@ -286,6 +287,29 @@ class OrderingTests(TestCase):
attrgetter("headline")
)
+ def test_alias_with_period_shadows_table_name(self):
+ """
+ Aliases with periods are not confused for table names from extra().
+ """
+ Article.objects.update(author=self.author_2)
+ Article.objects.create(
+ headline="Backdated", pub_date=datetime(1900, 1, 1), author=self.author_1
+ )
+ crafted = "ordering_article.pub_date"
+
+ qs = Article.objects.annotate(**{crafted: F("author")}).order_by("-" + crafted)
+ self.assertNotEqual(qs[0].headline, "Backdated")
+
+ relation = FilteredRelation("author")
+ qs2 = Article.objects.annotate(**{crafted: relation}).order_by(crafted)
+ with self.assertRaises(DatabaseError):
+ # Before, unlike F(), which causes ordering expressions to be
+ # replaced by ordinals like n in ORDER BY n, these were ordered by
+ # pub_date instead of author.
+ # The Article model orders by -pk, so sorting on author will place
+ # first any article by author2 instead of the backdated one.
+ self.assertNotEqual(qs2[0].headline, "Backdated")
+
def test_order_by_pk(self):
"""
'pk' works as an ordering option in Meta.
Index: Django-2.2.28/tests/queries/tests.py
===================================================================
--- Django-2.2.28.orig/tests/queries/tests.py
+++ Django-2.2.28/tests/queries/tests.py
@@ -634,14 +634,6 @@ class Queries1Tests(TestCase):
['datetime.datetime(2007, 12, 19, 0, 0)']
)
- def test_ticket7098(self):
- # Make sure semi-deprecated ordering by related models syntax still
- # works.
- self.assertSequenceEqual(
- Item.objects.values('note__note').order_by('queries_note.note', 'id'),
- [{'note__note': 'n2'}, {'note__note': 'n3'}, {'note__note': 'n3'}, {'note__note': 'n3'}]
- )
-
def test_ticket7096(self):
# Make sure exclude() with multiple conditions continues to work.
self.assertQuerysetEqual(