티스토리 수익 글 보기

티스토리 수익 글 보기

[4.2.x] Fixed CVE-2026-1312 — Protected order_by() from SQL injectio… · django/django@90f5b10 · GitHub
Skip to content
/ django Public

Commit 90f5b10

Browse files
[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 69065ca from main.
1 parent f75f8f3 commit 90f5b10

File tree

3 files changed

+36
1
lines changed

3 files changed

+36
1
lines changed

django/db/models/sql/compiler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ def _order_by_pairs(self):
402402
yield OrderBy(expr, descending=descending), False
403403
continue
404404

405-
if "." in field:
405+
if "." in field and field in self.query.extra_order_by:
406406
# This came in through an extra(order_by=...) addition. Pass it
407407
# on verbatim.
408408
table, col = col.split(".", 1)

docs/releases/4.2.28.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,13 @@ expansion, as the ``**kwargs`` passed to :meth:`.QuerySet.annotate`,
7070

7171
This issue has severity "high" according to the :ref:`Django security policy
7272
<security-disclosure>`.
73+
74+
CVE-2026-1312: Potential SQL injection via ``QuerySet.order_by`` and ``FilteredRelation``
75+
=========================================================================================
76+
77+
:meth:`.QuerySet.order_by` was subject to SQL injection in column aliases
78+
containing periods when the same alias was, using a suitably crafted
79+
dictionary, with dictionary expansion, used in :class:`.FilteredRelation`.
80+
81+
This issue has severity "high" according to the :ref:`Django security policy
82+
<security-disclosure>`.

tests/ordering/tests.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77
Count,
88
DateTimeField,
99
F,
10+
FilteredRelation,
1011
Max,
1112
OrderBy,
1213
OuterRef,
1314
Subquery,
1415
Value,
1516
)
1617
from django.db.models.functions import Length, Upper
18+
from django.db.utils import DatabaseError
1719
from django.test import TestCase
1820

1921
from .models import (
@@ -392,6 +394,29 @@ def test_extra_ordering_with_table_name(self):
392394
attrgetter("headline"),
393395
)
394396

397+
def test_alias_with_period_shadows_table_name(self):
398+
"""
399+
Aliases with periods are not confused for table names from extra().
400+
"""
401+
Article.objects.update(author=self.author_2)
402+
Article.objects.create(
403+
headline="Backdated", pub_date=datetime(1900, 1, 1), author=self.author_1
404+
)
405+
crafted = "ordering_article.pub_date"
406+
407+
qs = Article.objects.annotate(**{crafted: F("author")}).order_by("-" + crafted)
408+
self.assertNotEqual(qs[0].headline, "Backdated")
409+
410+
relation = FilteredRelation("author")
411+
qs2 = Article.objects.annotate(**{crafted: relation}).order_by(crafted)
412+
with self.assertRaises(DatabaseError):
413+
# Before, unlike F(), which causes ordering expressions to be
414+
# replaced by ordinals like n in ORDER BY n, these were ordered by
415+
# pub_date instead of author.
416+
# The Article model orders by -pk, so sorting on author will place
417+
# first any article by author2 instead of the backdated one.
418+
self.assertNotEqual(qs2[0].headline, "Backdated")
419+
395420
def test_order_by_pk(self):
396421
"""
397422
'pk' works as an ordering option in Meta.

0 commit comments

Comments
 (0)