CVE-2025-13473 CVE-2025-14550 CVE-2026-1207 CVE-2026-1285 CVE-2026-1287 CVE-2026-1312 #4
121
CVE-2025-13473.patch
Normal file
121
CVE-2025-13473.patch
Normal file
@@ -0,0 +1,121 @@
|
||||
From 184e38ab0a061c365f5775676a074796d8abd02f Mon Sep 17 00:00:00 2001
|
||||
From: Jake Howard <git@theorangeone.net>
|
||||
Date: Wed, 19 Nov 2025 16:52:28 +0000
|
||||
Subject: [PATCH] [5.2.x] Fixed CVE-2025-13473 -- Standardized timing of
|
||||
check_password() in mod_wsgi auth handler.
|
||||
|
||||
Refs CVE-2024-39329, #20760.
|
||||
|
||||
Thanks Stackered for the report, and Jacob Walls and Markus Holtermann
|
||||
for the reviews.
|
||||
|
||||
Co-authored-by: Natalia <124304+nessita@users.noreply.github.com>
|
||||
|
||||
Backport of 3eb814e02a4c336866d4189fa0c24fd1875863ed from main.
|
||||
---
|
||||
django/contrib/auth/handlers/modwsgi.py | 37 ++++++++++++++++++++-----
|
||||
docs/releases/4.2.28.txt | 10 +++++++
|
||||
docs/releases/5.2.11.txt | 10 +++++++
|
||||
tests/auth_tests/test_handlers.py | 28 +++++++++++++++++++
|
||||
4 files changed, 78 insertions(+), 7 deletions(-)
|
||||
|
||||
diff --git a/django/contrib/auth/handlers/modwsgi.py b/django/contrib/auth/handlers/modwsgi.py
|
||||
index 591ec72cb4cd..086db89fc846 100644
|
||||
--- a/django/contrib/auth/handlers/modwsgi.py
|
||||
+++ b/django/contrib/auth/handlers/modwsgi.py
|
||||
@@ -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()
|
||||
|
||||
diff --git a/tests/auth_tests/test_handlers.py b/tests/auth_tests/test_handlers.py
|
||||
index a6b53a9ef11d..32b4371198d4 100644
|
||||
--- a/tests/auth_tests/test_handlers.py
|
||||
+++ b/tests/auth_tests/test_handlers.py
|
||||
@@ -1,4 +1,7 @@
|
||||
+from unittest import mock
|
||||
+
|
||||
from django.contrib.auth.handlers.modwsgi import check_password, groups_for_user
|
||||
+from django.contrib.auth.hashers import get_hasher
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.test import TransactionTestCase, override_settings
|
||||
|
||||
@@ -73,3 +76,28 @@ def test_groups_for_user(self):
|
||||
|
||||
self.assertEqual(groups_for_user({}, "test"), [b"test_group"])
|
||||
self.assertEqual(groups_for_user({}, "test1"), [])
|
||||
+
|
||||
+ def test_check_password_fake_runtime(self):
|
||||
+ """
|
||||
+ Hasher is run once regardless of whether the user exists. Refs #20760.
|
||||
+ """
|
||||
+ User.objects.create_user("test", "test@example.com", "test")
|
||||
+ User.objects.create_user("inactive", "test@nono.com", "test", is_active=False)
|
||||
+ User.objects.create_user("unusable", "test@nono.com")
|
||||
+
|
||||
+ hasher = get_hasher()
|
||||
+
|
||||
+ for username, password in [
|
||||
+ ("test", "test"),
|
||||
+ ("test", "wrong"),
|
||||
+ ("inactive", "test"),
|
||||
+ ("inactive", "wrong"),
|
||||
+ ("unusable", "test"),
|
||||
+ ("doesnotexist", "test"),
|
||||
+ ]:
|
||||
+ with (
|
||||
+ self.subTest(username=username, password=password),
|
||||
+ mock.patch.object(hasher, "encode") as mock_make_password,
|
||||
+ ):
|
||||
+ check_password({}, username, password)
|
||||
+ mock_make_password.assert_called_once()
|
||||
95
CVE-2025-14550.patch
Normal file
95
CVE-2025-14550.patch
Normal file
@@ -0,0 +1,95 @@
|
||||
From 1ba90069c12836db46981bdf75b0e661db5849ce Mon Sep 17 00:00:00 2001
|
||||
From: Jake Howard <git@theorangeone.net>
|
||||
Date: Wed, 14 Jan 2026 15:25:45 +0000
|
||||
Subject: [PATCH] [5.2.x] Fixed CVE-2025-14550 -- Optimized repeated header
|
||||
parsing in ASGI requests.
|
||||
|
||||
Thanks Jiyong Yang for the report, and Natalia Bidart, Jacob Walls, and
|
||||
Shai Berger for reviews.
|
||||
|
||||
Backport of eb22e1d6d643360e952609ef562c139a100ea4eb from main.
|
||||
---
|
||||
django/core/handlers/asgi.py | 7 ++++---
|
||||
docs/releases/4.2.28.txt | 12 ++++++++++++
|
||||
docs/releases/5.2.11.txt | 12 ++++++++++++
|
||||
tests/asgi/tests.py | 28 +++++++++++++++++++++++++++-
|
||||
4 files changed, 55 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/django/core/handlers/asgi.py b/django/core/handlers/asgi.py
|
||||
index bb6a6bfb3ce7..2dfcc7f31d3f 100644
|
||||
--- a/django/core/handlers/asgi.py
|
||||
+++ b/django/core/handlers/asgi.py
|
||||
@@ -3,6 +3,7 @@
|
||||
import sys
|
||||
import tempfile
|
||||
import traceback
|
||||
+from collections import defaultdict
|
||||
from contextlib import aclosing
|
||||
|
||||
from asgiref.sync import ThreadSensitiveContext, sync_to_async
|
||||
@@ -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":
|
||||
@@ -94,9 +96,8 @@ def __init__(self, scope, body_file):
|
||||
# HTTP/2 say only ASCII chars are allowed in headers, but decode
|
||||
# latin1 just in case.
|
||||
value = value.decode("latin1")
|
||||
- if corrected_name in self.META:
|
||||
- value = self.META[corrected_name] + "," + value
|
||||
- self.META[corrected_name] = value
|
||||
+ _headers[corrected_name].append(value)
|
||||
+ 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.
|
||||
diff --git a/tests/asgi/tests.py b/tests/asgi/tests.py
|
||||
index 0b1d3cd60879..81a53e539742 100644
|
||||
--- a/tests/asgi/tests.py
|
||||
+++ b/tests/asgi/tests.py
|
||||
@@ -211,7 +211,7 @@ async def test_post_body(self):
|
||||
self.assertEqual(response_body["type"], "http.response.body")
|
||||
self.assertEqual(response_body["body"], b"Echo!")
|
||||
|
||||
- async def test_create_request_error(self):
|
||||
+ async def test_request_too_big_request_error(self):
|
||||
# Track request_finished signal.
|
||||
signal_handler = SignalHandler()
|
||||
request_finished.connect(signal_handler)
|
||||
@@ -242,6 +242,32 @@ class TestASGIHandler(ASGIHandler):
|
||||
signal_handler.calls[0]["thread"], threading.current_thread()
|
||||
)
|
||||
|
||||
+ async def test_meta_not_modified_with_repeat_headers(self):
|
||||
+ scope = self.async_request_factory._base_scope(path="/", http_version="2.0")
|
||||
+ scope["headers"] = [(b"foo", b"bar")] * 200_000
|
||||
+
|
||||
+ setitem_count = 0
|
||||
+
|
||||
+ class InstrumentedDict(dict):
|
||||
+ def __setitem__(self, *args, **kwargs):
|
||||
+ nonlocal setitem_count
|
||||
+ setitem_count += 1
|
||||
+ super().__setitem__(*args, **kwargs)
|
||||
+
|
||||
+ class InstrumentedASGIRequest(ASGIRequest):
|
||||
+ @property
|
||||
+ def META(self):
|
||||
+ return self._meta
|
||||
+
|
||||
+ @META.setter
|
||||
+ def META(self, value):
|
||||
+ self._meta = InstrumentedDict(**value)
|
||||
+
|
||||
+ request = InstrumentedASGIRequest(scope, None)
|
||||
+
|
||||
+ self.assertEqual(len(request.headers["foo"].split(",")), 200_000)
|
||||
+ self.assertLessEqual(setitem_count, 100)
|
||||
+
|
||||
async def test_cancel_post_request_with_sync_processing(self):
|
||||
"""
|
||||
The request.body object should be available and readable in view
|
||||
106
CVE-2026-1207.patch
Normal file
106
CVE-2026-1207.patch
Normal file
@@ -0,0 +1,106 @@
|
||||
From 17a1d64a58ef24c0c3b78d66d86f5415075f18f0 Mon Sep 17 00:00:00 2001
|
||||
From: Jacob Walls <jacobtylerwalls@gmail.com>
|
||||
Date: Mon, 19 Jan 2026 15:42:33 -0500
|
||||
Subject: [PATCH] [5.2.x] Fixed CVE-2026-1207 -- Prevented SQL injections in
|
||||
RasterField lookups via band index.
|
||||
|
||||
Thanks Tarek Nakkouch for the report, and Simon Charette for the initial
|
||||
triage and review.
|
||||
|
||||
Backport of 81aa5292967cd09319c45fe2c1a525ce7b6684d8 from main.
|
||||
---
|
||||
.../gis/db/backends/postgis/operations.py | 6 +++
|
||||
docs/releases/4.2.28.txt | 12 +++++
|
||||
docs/releases/5.2.11.txt | 12 +++++
|
||||
tests/gis_tests/rasterapp/test_rasterfield.py | 47 ++++++++++++++++++-
|
||||
4 files changed, 76 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py
|
||||
index 7a347c52878a..c39f756c792e 100644
|
||||
--- a/django/contrib/gis/db/backends/postgis/operations.py
|
||||
+++ b/django/contrib/gis/db/backends/postgis/operations.py
|
||||
@@ -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 "
|
||||
@@ -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 "
|
||||
diff --git a/tests/gis_tests/rasterapp/test_rasterfield.py b/tests/gis_tests/rasterapp/test_rasterfield.py
|
||||
index 3f2ce770a9a6..89c4ec4856a7 100644
|
||||
--- a/tests/gis_tests/rasterapp/test_rasterfield.py
|
||||
+++ b/tests/gis_tests/rasterapp/test_rasterfield.py
|
||||
@@ -2,7 +2,11 @@
|
||||
|
||||
from django.contrib.gis.db.models.fields import BaseSpatialField
|
||||
from django.contrib.gis.db.models.functions import Distance
|
||||
-from django.contrib.gis.db.models.lookups import DistanceLookupBase, GISLookup
|
||||
+from django.contrib.gis.db.models.lookups import (
|
||||
+ DistanceLookupBase,
|
||||
+ GISLookup,
|
||||
+ RasterBandTransform,
|
||||
+)
|
||||
from django.contrib.gis.gdal import GDALRaster
|
||||
from django.contrib.gis.geos import GEOSGeometry
|
||||
from django.contrib.gis.measure import D
|
||||
@@ -356,6 +360,47 @@ def test_lookup_input_band_not_allowed(self):
|
||||
with self.assertRaisesMessage(ValueError, msg):
|
||||
qs.count()
|
||||
|
||||
+ def test_lookup_invalid_band_rhs(self):
|
||||
+ rast = GDALRaster(json.loads(JSON_RASTER))
|
||||
+ qs = RasterModel.objects.filter(rast__contains=(rast, "evil"))
|
||||
+ msg = "Band index must be an integer, but got 'str'."
|
||||
+ with self.assertRaisesMessage(TypeError, msg):
|
||||
+ qs.count()
|
||||
+
|
||||
+ def test_lookup_invalid_band_lhs(self):
|
||||
+ """
|
||||
+ Typical left-hand side usage is protected against non-integers, but for
|
||||
+ defense-in-depth purposes, construct custom lookups that evade the
|
||||
+ `int()` and `+ 1` checks in the lookups shipped by django.contrib.gis.
|
||||
+ """
|
||||
+
|
||||
+ # Evade the int() call in RasterField.get_transform().
|
||||
+ class MyRasterBandTransform(RasterBandTransform):
|
||||
+ band_index = "evil"
|
||||
+
|
||||
+ def process_band_indices(self, *args, **kwargs):
|
||||
+ self.band_lhs = self.lhs.band_index
|
||||
+ self.band_rhs, *self.rhs_params = self.rhs_params
|
||||
+
|
||||
+ # Evade the `+ 1` call in BaseSpatialField.process_band_indices().
|
||||
+ ContainsLookup = RasterModel._meta.get_field("rast").get_lookup("contains")
|
||||
+
|
||||
+ class MyContainsLookup(ContainsLookup):
|
||||
+ def process_band_indices(self, *args, **kwargs):
|
||||
+ self.band_lhs = self.lhs.band_index
|
||||
+ self.band_rhs, *self.rhs_params = self.rhs_params
|
||||
+
|
||||
+ RasterField = RasterModel._meta.get_field("rast")
|
||||
+ RasterField.register_lookup(MyContainsLookup, "contains")
|
||||
+ self.addCleanup(RasterField.register_lookup, ContainsLookup, "contains")
|
||||
+
|
||||
+ qs = RasterModel.objects.annotate(
|
||||
+ transformed=MyRasterBandTransform("rast")
|
||||
+ ).filter(transformed__contains=(F("transformed"), 1))
|
||||
+ msg = "Band index must be an integer, but got 'str'."
|
||||
+ with self.assertRaisesMessage(TypeError, msg):
|
||||
+ list(qs)
|
||||
+
|
||||
def test_isvalid_lookup_with_raster_error(self):
|
||||
qs = RasterModel.objects.filter(rast__isvalid=True)
|
||||
msg = (
|
||||
69
CVE-2026-1285.patch
Normal file
69
CVE-2026-1285.patch
Normal file
@@ -0,0 +1,69 @@
|
||||
From 9f2ada875bbee62ac46032e38ddb22755d67ae5a Mon Sep 17 00:00:00 2001
|
||||
From: Natalia <124304+nessita@users.noreply.github.com>
|
||||
Date: Wed, 21 Jan 2026 09:53:10 -0300
|
||||
Subject: [PATCH] [5.2.x] Fixed CVE-2026-1285 -- Mitigated potential DoS in
|
||||
django.utils.text.Truncator for HTML input.
|
||||
|
||||
The `TruncateHTMLParser` used `deque.remove()` to remove tags from the
|
||||
stack when processing end tags. With crafted input containing many
|
||||
unmatched end tags, this caused repeated full scans of the tag stack,
|
||||
leading to quadratic time complexity.
|
||||
|
||||
The fix uses LIFO semantics, only removing a tag from the stack when it
|
||||
matches the most recently opened tag. This avoids linear scans for
|
||||
unmatched end tags and reduces complexity to linear time.
|
||||
|
||||
Refs #30686 and 6ee37ada3241ed263d8d1c2901b030d964cbd161.
|
||||
|
||||
Thanks Seokchan Yoon for the report, and Jake Howard and Jacob Walls for
|
||||
reviews.
|
||||
|
||||
Backport of a33540b3e20b5d759aa8b2e4b9ca0e8edd285344 from main.
|
||||
---
|
||||
django/utils/text.py | 9 +++++----
|
||||
docs/releases/4.2.28.txt | 12 ++++++++++++
|
||||
docs/releases/5.2.11.txt | 12 ++++++++++++
|
||||
tests/utils_tests/test_text.py | 10 ++++++++++
|
||||
4 files changed, 39 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/django/utils/text.py b/django/utils/text.py
|
||||
index 26edde99e336..21efb00b98e8 100644
|
||||
--- a/django/utils/text.py
|
||||
+++ b/django/utils/text.py
|
||||
@@ -126,10 +126,11 @@ def handle_starttag(self, tag, attrs):
|
||||
def handle_endtag(self, tag):
|
||||
if tag not in self.void_elements:
|
||||
self.output += 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)
|
||||
diff --git a/tests/utils_tests/test_text.py b/tests/utils_tests/test_text.py
|
||||
index 63c7889cbcec..11c01874cb5d 100644
|
||||
--- a/tests/utils_tests/test_text.py
|
||||
+++ b/tests/utils_tests/test_text.py
|
||||
@@ -202,6 +202,16 @@ def test_truncate_chars_html_with_html_entities(self):
|
||||
truncator = text.Truncator("<p>I <3 python, what about you?</p>")
|
||||
self.assertEqual("<p>I <3 python, wh…</p>", truncator.chars(16, html=True))
|
||||
|
||||
+ def test_truncate_chars_html_with_misnested_tags(self):
|
||||
+ # LIFO removal keeps all tags when a middle tag is closed out of order.
|
||||
+ # With <a><b><c></b>, the </b> doesn't match <c>, so all tags remain
|
||||
+ # in the stack and are properly closed at truncation.
|
||||
+ truncator = text.Truncator("<a><b><c></b>XXXX")
|
||||
+ self.assertEqual(
|
||||
+ truncator.chars(2, html=True, truncate=""),
|
||||
+ "<a><b><c></b>XX</c></b></a>",
|
||||
+ )
|
||||
+
|
||||
def test_truncate_words(self):
|
||||
truncator = text.Truncator("The quick brown fox jumped over the lazy dog.")
|
||||
self.assertEqual(
|
||||
301
CVE-2026-1287.patch
Normal file
301
CVE-2026-1287.patch
Normal file
@@ -0,0 +1,301 @@
|
||||
From 3e68ccdc11c127758745ddf0b4954990b14892bc Mon Sep 17 00:00:00 2001
|
||||
From: Jake Howard <git@theorangeone.net>
|
||||
Date: Wed, 21 Jan 2026 11:14:48 +0000
|
||||
Subject: [PATCH] [5.2.x] Fixed CVE-2026-1287 -- Protected against SQL
|
||||
injection in column aliases via control characters.
|
||||
|
||||
Control characters in FilteredRelation column aliases could be used for
|
||||
SQL injection attacks. This affected QuerySet.annotate(), aggregate(),
|
||||
extra(), values(), values_list(), and alias() when using dictionary
|
||||
expansion with **kwargs.
|
||||
|
||||
Thanks Solomon Kebede for the report, and Simon Charette, Jacob Walls,
|
||||
and Natalia Bidart for reviews.
|
||||
|
||||
Backport of e891a84c7ef9962bfcc3b4685690219542f86a22 from main.
|
||||
---
|
||||
django/db/models/sql/query.py | 10 +--
|
||||
docs/releases/4.2.28.txt | 13 ++++
|
||||
docs/releases/5.2.11.txt | 13 ++++
|
||||
tests/aggregation/tests.py | 18 ++++--
|
||||
tests/annotations/tests.py | 74 +++++++++++++++--------
|
||||
tests/expressions/test_queryset_values.py | 36 +++++++----
|
||||
tests/queries/tests.py | 18 ++++--
|
||||
7 files changed, 128 insertions(+), 54 deletions(-)
|
||||
|
||||
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
|
||||
index 3a1cd739511e..baeac3a05be2 100644
|
||||
--- a/django/db/models/sql/query.py
|
||||
+++ b/django/db/models/sql/query.py
|
||||
@@ -48,9 +48,11 @@
|
||||
|
||||
__all__ = ["Query", "RawQuery"]
|
||||
|
||||
-# 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, 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
|
||||
@@ -1209,7 +1211,7 @@ def check_alias(self, alias):
|
||||
if FORBIDDEN_ALIAS_PATTERN.search(alias):
|
||||
raise ValueError(
|
||||
"Column aliases cannot contain whitespace characters, hashes, "
|
||||
- "quotation marks, semicolons, or SQL comments."
|
||||
+ "control characters, quotation marks, semicolons, or SQL comments."
|
||||
)
|
||||
|
||||
def add_annotation(self, annotation, alias, select=True):
|
||||
diff --git a/tests/aggregation/tests.py b/tests/aggregation/tests.py
|
||||
index 2e41f1994723..f621c53e918a 100644
|
||||
--- a/tests/aggregation/tests.py
|
||||
+++ b/tests/aggregation/tests.py
|
||||
@@ -2,6 +2,7 @@
|
||||
import math
|
||||
import re
|
||||
from decimal import Decimal
|
||||
+from itertools import chain
|
||||
|
||||
from django.core.exceptions import FieldError
|
||||
from django.db import connection
|
||||
@@ -2134,13 +2135,18 @@ def test_exists_none_with_aggregate(self):
|
||||
self.assertEqual(len(qs), 6)
|
||||
|
||||
def test_alias_sql_injection(self):
|
||||
- crafted_alias = """injected_name" from "aggregation_author"; --"""
|
||||
msg = (
|
||||
- "Column aliases cannot contain whitespace characters, hashes, quotation "
|
||||
- "marks, semicolons, or SQL comments."
|
||||
- )
|
||||
- with self.assertRaisesMessage(ValueError, msg):
|
||||
- Author.objects.aggregate(**{crafted_alias: Avg("age")})
|
||||
+ "Column aliases cannot contain whitespace characters, hashes, "
|
||||
+ "control characters, quotation marks, semicolons, or SQL comments."
|
||||
+ )
|
||||
+ for crafted_alias in [
|
||||
+ """injected_name" from "aggregation_author"; --""",
|
||||
+ # Control characters.
|
||||
+ *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))),
|
||||
+ ]:
|
||||
+ with self.subTest(crafted_alias):
|
||||
+ with self.assertRaisesMessage(ValueError, msg):
|
||||
+ Author.objects.aggregate(**{crafted_alias: Avg("age")})
|
||||
|
||||
def test_exists_extra_where_with_aggregate(self):
|
||||
qs = Book.objects.annotate(
|
||||
diff --git a/tests/annotations/tests.py b/tests/annotations/tests.py
|
||||
index 78e5408d0fe7..0430c68f9175 100644
|
||||
--- a/tests/annotations/tests.py
|
||||
+++ b/tests/annotations/tests.py
|
||||
@@ -1,5 +1,6 @@
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
+from itertools import chain
|
||||
from unittest import skipUnless
|
||||
|
||||
from django.core.exceptions import FieldDoesNotExist, FieldError
|
||||
@@ -1157,22 +1158,32 @@ def test_annotation_aggregate_with_m2o(self):
|
||||
)
|
||||
|
||||
def test_alias_sql_injection(self):
|
||||
- crafted_alias = """injected_name" from "annotations_book"; --"""
|
||||
msg = (
|
||||
- "Column aliases cannot contain whitespace characters, hashes, quotation "
|
||||
- "marks, semicolons, or SQL comments."
|
||||
- )
|
||||
- with self.assertRaisesMessage(ValueError, msg):
|
||||
- Book.objects.annotate(**{crafted_alias: Value(1)})
|
||||
+ "Column aliases cannot contain whitespace characters, hashes, "
|
||||
+ "control characters, quotation marks, semicolons, or SQL comments."
|
||||
+ )
|
||||
+ for crafted_alias in [
|
||||
+ """injected_name" from "annotations_book"; --""",
|
||||
+ # Control characters.
|
||||
+ *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))),
|
||||
+ ]:
|
||||
+ with self.subTest(crafted_alias):
|
||||
+ with self.assertRaisesMessage(ValueError, msg):
|
||||
+ Book.objects.annotate(**{crafted_alias: Value(1)})
|
||||
|
||||
def test_alias_filtered_relation_sql_injection(self):
|
||||
- crafted_alias = """injected_name" from "annotations_book"; --"""
|
||||
msg = (
|
||||
- "Column aliases cannot contain whitespace characters, hashes, quotation "
|
||||
- "marks, semicolons, or SQL comments."
|
||||
- )
|
||||
- with self.assertRaisesMessage(ValueError, msg):
|
||||
- Book.objects.annotate(**{crafted_alias: FilteredRelation("author")})
|
||||
+ "Column aliases cannot contain whitespace characters, hashes, "
|
||||
+ "control characters, quotation marks, semicolons, or SQL comments."
|
||||
+ )
|
||||
+ for crafted_alias in [
|
||||
+ """injected_name" from "annotations_book"; --""",
|
||||
+ # Control characters.
|
||||
+ *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))),
|
||||
+ ]:
|
||||
+ with self.subTest(crafted_alias):
|
||||
+ with self.assertRaisesMessage(ValueError, msg):
|
||||
+ Book.objects.annotate(**{crafted_alias: FilteredRelation("author")})
|
||||
|
||||
def test_alias_forbidden_chars(self):
|
||||
tests = [
|
||||
@@ -1190,10 +1201,11 @@ def test_alias_forbidden_chars(self):
|
||||
"alias[",
|
||||
"alias]",
|
||||
"ali#as",
|
||||
+ "ali\0as",
|
||||
]
|
||||
msg = (
|
||||
- "Column aliases cannot contain whitespace characters, hashes, quotation "
|
||||
- "marks, semicolons, or SQL comments."
|
||||
+ "Column aliases cannot contain whitespace characters, hashes, "
|
||||
+ "control characters, quotation marks, semicolons, or SQL comments."
|
||||
)
|
||||
for crafted_alias in tests:
|
||||
with self.subTest(crafted_alias):
|
||||
@@ -1491,22 +1503,32 @@ def test_alias_after_values(self):
|
||||
self.assertEqual(qs.get(pk=self.b1.pk), (self.b1.pk,))
|
||||
|
||||
def test_alias_sql_injection(self):
|
||||
- crafted_alias = """injected_name" from "annotations_book"; --"""
|
||||
msg = (
|
||||
- "Column aliases cannot contain whitespace characters, hashes, quotation "
|
||||
- "marks, semicolons, or SQL comments."
|
||||
- )
|
||||
- with self.assertRaisesMessage(ValueError, msg):
|
||||
- Book.objects.alias(**{crafted_alias: Value(1)})
|
||||
+ "Column aliases cannot contain whitespace characters, hashes, "
|
||||
+ "control characters, quotation marks, semicolons, or SQL comments."
|
||||
+ )
|
||||
+ for crafted_alias in [
|
||||
+ """injected_name" from "annotations_book"; --""",
|
||||
+ # Control characters.
|
||||
+ *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))),
|
||||
+ ]:
|
||||
+ with self.subTest(crafted_alias):
|
||||
+ with self.assertRaisesMessage(ValueError, msg):
|
||||
+ Book.objects.alias(**{crafted_alias: Value(1)})
|
||||
|
||||
def test_alias_filtered_relation_sql_injection(self):
|
||||
- crafted_alias = """injected_name" from "annotations_book"; --"""
|
||||
msg = (
|
||||
- "Column aliases cannot contain whitespace characters, hashes, quotation "
|
||||
- "marks, semicolons, or SQL comments."
|
||||
- )
|
||||
- with self.assertRaisesMessage(ValueError, msg):
|
||||
- Book.objects.alias(**{crafted_alias: FilteredRelation("authors")})
|
||||
+ "Column aliases cannot contain whitespace characters, hashes, "
|
||||
+ "control characters, quotation marks, semicolons, or SQL comments."
|
||||
+ )
|
||||
+ for crafted_alias in [
|
||||
+ """injected_name" from "annotations_book"; --""",
|
||||
+ # Control characters.
|
||||
+ *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))),
|
||||
+ ]:
|
||||
+ with self.subTest(crafted_alias):
|
||||
+ with self.assertRaisesMessage(ValueError, msg):
|
||||
+ Book.objects.alias(**{crafted_alias: FilteredRelation("authors")})
|
||||
|
||||
def test_alias_filtered_relation_sql_injection_dollar_sign(self):
|
||||
qs = Book.objects.alias(
|
||||
diff --git a/tests/expressions/test_queryset_values.py b/tests/expressions/test_queryset_values.py
|
||||
index 080ee06183dc..afd8a51159d5 100644
|
||||
--- a/tests/expressions/test_queryset_values.py
|
||||
+++ b/tests/expressions/test_queryset_values.py
|
||||
@@ -1,3 +1,5 @@
|
||||
+from itertools import chain
|
||||
+
|
||||
from django.db.models import F, Sum
|
||||
from django.test import TestCase, skipUnlessDBFeature
|
||||
|
||||
@@ -35,26 +37,36 @@ def test_values_expression(self):
|
||||
)
|
||||
|
||||
def test_values_expression_alias_sql_injection(self):
|
||||
- crafted_alias = """injected_name" from "expressions_company"; --"""
|
||||
msg = (
|
||||
- "Column aliases cannot contain whitespace characters, hashes, quotation "
|
||||
- "marks, semicolons, or SQL comments."
|
||||
+ "Column aliases cannot contain whitespace characters, hashes, "
|
||||
+ "control characters, quotation marks, semicolons, or SQL comments."
|
||||
)
|
||||
- with self.assertRaisesMessage(ValueError, msg):
|
||||
- Company.objects.values(**{crafted_alias: F("ceo__salary")})
|
||||
+ for crafted_alias in [
|
||||
+ """injected_name" from "expressions_company"; --""",
|
||||
+ # Control characters.
|
||||
+ *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))),
|
||||
+ ]:
|
||||
+ with self.subTest(crafted_alias):
|
||||
+ with self.assertRaisesMessage(ValueError, msg):
|
||||
+ Company.objects.values(**{crafted_alias: F("ceo__salary")})
|
||||
|
||||
@skipUnlessDBFeature("supports_json_field")
|
||||
def test_values_expression_alias_sql_injection_json_field(self):
|
||||
- crafted_alias = """injected_name" from "expressions_company"; --"""
|
||||
msg = (
|
||||
- "Column aliases cannot contain whitespace characters, hashes, quotation "
|
||||
- "marks, semicolons, or SQL comments."
|
||||
+ "Column aliases cannot contain whitespace characters, hashes, "
|
||||
+ "control characters, quotation marks, semicolons, or SQL comments."
|
||||
)
|
||||
- with self.assertRaisesMessage(ValueError, msg):
|
||||
- JSONFieldModel.objects.values(f"data__{crafted_alias}")
|
||||
+ for crafted_alias in [
|
||||
+ """injected_name" from "expressions_company"; --""",
|
||||
+ # Control characters.
|
||||
+ *(chr(c) for c in chain(range(32), range(0x7F, 0xA0))),
|
||||
+ ]:
|
||||
+ with self.subTest(crafted_alias):
|
||||
+ with self.assertRaisesMessage(ValueError, msg):
|
||||
+ JSONFieldModel.objects.values(f"data__{crafted_alias}")
|
||||
|
||||
- with self.assertRaisesMessage(ValueError, msg):
|
||||
- JSONFieldModel.objects.values_list(f"data__{crafted_alias}")
|
||||
+ with self.assertRaisesMessage(ValueError, msg):
|
||||
+ JSONFieldModel.objects.values_list(f"data__{crafted_alias}")
|
||||
|
||||
def test_values_expression_group_by(self):
|
||||
# values() applies annotate() first, so values selected are grouped by
|
||||
diff --git a/tests/queries/tests.py b/tests/queries/tests.py
|
||||
index c1589669b073..69d728b1cd36 100644
|
||||
--- a/tests/queries/tests.py
|
||||
+++ b/tests/queries/tests.py
|
||||
@@ -2,6 +2,7 @@
|
||||
import pickle
|
||||
import sys
|
||||
import unittest
|
||||
+from itertools import chain
|
||||
from operator import attrgetter
|
||||
|
||||
from django.core.exceptions import EmptyResultSet, FieldError, FullResultSet
|
||||
@@ -1959,13 +1960,18 @@ def test_extra_select_literal_percent_s(self):
|
||||
)
|
||||
|
||||
def test_extra_select_alias_sql_injection(self):
|
||||
- crafted_alias = """injected_name" from "queries_note"; --"""
|
||||
msg = (
|
||||
- "Column aliases cannot contain whitespace characters, hashes, quotation "
|
||||
- "marks, semicolons, or SQL comments."
|
||||
- )
|
||||
- with self.assertRaisesMessage(ValueError, msg):
|
||||
- Note.objects.extra(select={crafted_alias: "1"})
|
||||
+ "Column aliases cannot contain whitespace characters, hashes, "
|
||||
+ "control characters, quotation marks, semicolons, or SQL comments."
|
||||
+ )
|
||||
+ for crafted_alias in [
|
||||
+ """injected_name" from "queries_note"; --""",
|
||||
+ # Control characters.
|
||||
+ *(f"name{chr(c)}" for c in chain(range(32), range(0x7F, 0xA0))),
|
||||
+ ]:
|
||||
+ with self.subTest(crafted_alias):
|
||||
+ with self.assertRaisesMessage(ValueError, msg):
|
||||
+ Note.objects.extra(select={crafted_alias: "1"})
|
||||
|
||||
def test_queryset_reuse(self):
|
||||
# Using querysets doesn't mutate aliases.
|
||||
93
CVE-2026-1312-followup.patch
Normal file
93
CVE-2026-1312-followup.patch
Normal file
@@ -0,0 +1,93 @@
|
||||
From ab0ad8d39555292b55123adeac57ed64c776f8d9 Mon Sep 17 00:00:00 2001
|
||||
From: Jacob Walls <jacobtylerwalls@gmail.com>
|
||||
Date: Wed, 21 Jan 2026 18:00:13 -0500
|
||||
Subject: [PATCH] [5.2.x] Refs CVE-2026-1312 -- Raised ValueError when
|
||||
FilteredRelation aliases contain periods.
|
||||
|
||||
This prevents failures at the database layer, given that aliases in the
|
||||
ON clause are not quoted.
|
||||
|
||||
Systematically quoting aliases even in FilteredRelation is tracked in
|
||||
https://code.djangoproject.com/ticket/36795.
|
||||
|
||||
Backport of 005d60d97c4dfb117503bdb6f2facfcaf9315d84 from main.
|
||||
---
|
||||
django/db/models/sql/query.py | 5 +++++
|
||||
tests/filtered_relation/tests.py | 13 +++++++++++++
|
||||
tests/ordering/tests.py | 11 ++++++++---
|
||||
3 files changed, 26 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
|
||||
index baeac3a05be2..324e605b8eea 100644
|
||||
--- a/django/db/models/sql/query.py
|
||||
+++ b/django/db/models/sql/query.py
|
||||
@@ -1698,6 +1698,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(
|
||||
diff --git a/tests/filtered_relation/tests.py b/tests/filtered_relation/tests.py
|
||||
index a9c6e00567cf..a4561d59f95f 100644
|
||||
--- a/tests/filtered_relation/tests.py
|
||||
+++ b/tests/filtered_relation/tests.py
|
||||
@@ -216,6 +216,19 @@ def test_internal_queryset_alias_mapping(self):
|
||||
str(queryset.query),
|
||||
)
|
||||
|
||||
+ def test_period_forbidden(self):
|
||||
+ msg = (
|
||||
+ "FilteredRelation doesn't support aliases with periods (got 'book.alice')."
|
||||
+ )
|
||||
+ with self.assertRaisesMessage(ValueError, msg):
|
||||
+ Author.objects.annotate(
|
||||
+ **{
|
||||
+ "book.alice": FilteredRelation(
|
||||
+ "book", condition=Q(book__title__iexact="poem by alice")
|
||||
+ )
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
def test_multiple(self):
|
||||
qs = (
|
||||
Author.objects.annotate(
|
||||
diff --git a/tests/ordering/tests.py b/tests/ordering/tests.py
|
||||
index 530a27920e9d..30da025875bd 100644
|
||||
--- a/tests/ordering/tests.py
|
||||
+++ b/tests/ordering/tests.py
|
||||
@@ -15,7 +15,6 @@
|
||||
Value,
|
||||
)
|
||||
from django.db.models.functions import Length, Upper
|
||||
-from django.db.utils import DatabaseError
|
||||
from django.test import TestCase
|
||||
|
||||
from .models import (
|
||||
@@ -408,13 +407,19 @@ def test_alias_with_period_shadows_table_name(self):
|
||||
self.assertNotEqual(qs[0].headline, "Backdated")
|
||||
|
||||
relation = FilteredRelation("author")
|
||||
- qs2 = Article.objects.annotate(**{crafted: relation}).order_by(crafted)
|
||||
- with self.assertRaises(DatabaseError):
|
||||
+ msg = (
|
||||
+ "FilteredRelation doesn't support aliases with periods "
|
||||
+ "(got 'ordering_article.pub_date')."
|
||||
+ )
|
||||
+ with self.assertRaisesMessage(ValueError, msg):
|
||||
+ qs2 = Article.objects.annotate(**{crafted: relation}).order_by(crafted)
|
||||
# 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.
|
||||
+ # This assertion is reachable if FilteredRelation.__init__() starts
|
||||
+ # supporting periods in aliases in the future.
|
||||
self.assertNotEqual(qs2[0].headline, "Backdated")
|
||||
|
||||
def test_order_by_pk(self):
|
||||
92
CVE-2026-1312.patch
Normal file
92
CVE-2026-1312.patch
Normal file
@@ -0,0 +1,92 @@
|
||||
From e863ee273c6553e9b6fa4960a17acb535851857b Mon Sep 17 00:00:00 2001
|
||||
From: Jacob Walls <jacobtylerwalls@gmail.com>
|
||||
Date: Wed, 21 Jan 2026 17:53:52 -0500
|
||||
Subject: [PATCH] [5.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 69065ca869b0970dff8fdd8fafb390bf8b3bf222 from main.
|
||||
---
|
||||
django/db/models/sql/compiler.py | 2 +-
|
||||
docs/releases/4.2.28.txt | 10 ++++++++++
|
||||
docs/releases/5.2.11.txt | 10 ++++++++++
|
||||
tests/ordering/tests.py | 25 +++++++++++++++++++++++++
|
||||
4 files changed, 46 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
|
||||
index 4292243c71ee..7dbdec363568 100644
|
||||
--- a/django/db/models/sql/compiler.py
|
||||
+++ b/django/db/models/sql/compiler.py
|
||||
@@ -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)
|
||||
diff --git a/tests/ordering/tests.py b/tests/ordering/tests.py
|
||||
index b29404ed77da..530a27920e9d 100644
|
||||
--- a/tests/ordering/tests.py
|
||||
+++ b/tests/ordering/tests.py
|
||||
@@ -7,6 +7,7 @@
|
||||
Count,
|
||||
DateTimeField,
|
||||
F,
|
||||
+ FilteredRelation,
|
||||
Max,
|
||||
OrderBy,
|
||||
OuterRef,
|
||||
@@ -14,6 +15,7 @@
|
||||
Value,
|
||||
)
|
||||
from django.db.models.functions import Length, Upper
|
||||
+from django.db.utils import DatabaseError
|
||||
from django.test import TestCase
|
||||
|
||||
from .models import (
|
||||
@@ -392,6 +394,29 @@ def test_extra_ordering_with_table_name(self):
|
||||
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.
|
||||
@@ -1,3 +1,22 @@
|
||||
-------------------------------------------------------------------
|
||||
Wed Feb 4 13:21:44 UTC 2026 - Markéta Machová <mmachova@suse.com>
|
||||
|
||||
- Add test_strip_tags_incomplete.patch to fix behaviour with changes
|
||||
in the Python interpreter
|
||||
- Rebase test_strip_tags.patch
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Wed Feb 4 11:00:19 UTC 2026 - Markéta Machová <mmachova@suse.com>
|
||||
|
||||
- Add security patches:
|
||||
* CVE-2025-14550.patch (bsc#1257403)
|
||||
* CVE-2026-1312.patch (bsc#1257408)
|
||||
* CVE-2026-1312-followup.patch (bsc#1257408)
|
||||
* CVE-2026-1287.patch (bsc#1257407)
|
||||
* CVE-2026-1207.patch (bsc#1257405)
|
||||
* CVE-2025-13473.patch (bsc#1257401)
|
||||
* CVE-2026-1285.patch (bsc#1257406)
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Mon Dec 8 11:04:00 UTC 2025 - Markéta Machová <mmachova@suse.com>
|
||||
|
||||
|
||||
@@ -30,8 +30,11 @@ Source: https://www.djangoproject.com/m/releases/5.2/django-%{version}.t
|
||||
Source1: https://www.djangoproject.com/m/pgp/Django-%{version}.checksum.txt
|
||||
Source2: %{name}.keyring
|
||||
Source99: python-Django-rpmlintrc
|
||||
# PATCH-FIX-UPSTREAM https://github.com/django/django/pull/20390 Refs #36499 -- Adjusted test_strip_tags following Python behavior change for incomplete entities.
|
||||
Patch100: test_strip_tags_incomplete.patch
|
||||
# PATCH-FIX-UPSTREAM https://github.com/django/django/pull/19639 Fixed #36499 -- Adjusted utils_tests.test_html.TestUtilsHtml.test_strip_tags following Python's HTMLParser new behavior.
|
||||
Patch0: test_strip_tags.patch
|
||||
# fixed and refined upstream, but some of our interpreters weren't updated to a new version yet and still only carry the patch, so providing the non-conditional version
|
||||
Patch101: test_strip_tags.patch
|
||||
# PATCH-FIX-UPSTREAM CVE-2025-57833.patch bsc#1248810
|
||||
Patch1: CVE-2025-57833.patch
|
||||
# PATCH-FIX-UPSTREAM CVE-2025-59681.patch bsc#1250485
|
||||
@@ -44,6 +47,20 @@ Patch4: CVE-2025-64459.patch
|
||||
Patch5: CVE-2025-13372.patch
|
||||
# PATCH-FIX-UPSTREAM CVE-2025-64460.patch bsc#1254437
|
||||
Patch6: CVE-2025-64460.patch
|
||||
# PATCH-FIX-UPSTREAM CVE-2025-13473.patch bsc#1257401
|
||||
Patch7: CVE-2025-13473.patch
|
||||
# PATCH-FIX-UPSTREAM CVE-2025-14550.patch bsc#1257403
|
||||
Patch8: CVE-2025-14550.patch
|
||||
# PATCH-FIX-UPSTREAM CVE-2026-1207.patch bsc#1257405
|
||||
Patch9: CVE-2026-1207.patch
|
||||
# PATCH-FIX-UPSTREAM CVE-2026-1285.patch bsc#1257406
|
||||
Patch10: CVE-2026-1285.patch
|
||||
# PATCH-FIX-UPSTREAM CVE-2026-1287.patch bsc#1257407
|
||||
Patch11: CVE-2026-1287.patch
|
||||
# PATCH-FIX-UPSTREAM CVE-2026-1312.patch bsc#1257408
|
||||
Patch12: CVE-2026-1312.patch
|
||||
# PATCH-FIX-UPSTREAM CVE-2026-1312-followup.patch bsc#1257408
|
||||
Patch13: CVE-2026-1312-followup.patch
|
||||
BuildRequires: %{python_module Jinja2 >= 2.9.2}
|
||||
BuildRequires: %{python_module Pillow >= 6.2.0}
|
||||
BuildRequires: %{python_module PyYAML}
|
||||
|
||||
@@ -10,11 +10,11 @@ Subject: [PATCH] Fixed #36499 -- Adjusted
|
||||
tests/utils_tests/test_html.py | 4 ++--
|
||||
2 files changed, 4 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/tests/test_utils/tests.py b/tests/test_utils/tests.py
|
||||
index 494a0ea8d384..0de19eae9072 100644
|
||||
--- a/tests/test_utils/tests.py
|
||||
+++ b/tests/test_utils/tests.py
|
||||
@@ -959,10 +959,10 @@ def test_parsing_errors(self):
|
||||
Index: django-5.2.4/tests/test_utils/tests.py
|
||||
===================================================================
|
||||
--- django-5.2.4.orig/tests/test_utils/tests.py
|
||||
+++ django-5.2.4/tests/test_utils/tests.py
|
||||
@@ -945,10 +945,10 @@ class HTMLEqualTests(SimpleTestCase):
|
||||
self.assertHTMLEqual("", "<p>")
|
||||
error_msg = (
|
||||
"First argument is not valid HTML:\n"
|
||||
@@ -27,14 +27,14 @@ index 494a0ea8d384..0de19eae9072 100644
|
||||
with self.assertRaises(HTMLParseError):
|
||||
parse_html("</p>")
|
||||
|
||||
diff --git a/tests/utils_tests/test_html.py b/tests/utils_tests/test_html.py
|
||||
index 4ce552e79a0d..205eaeca1668 100644
|
||||
--- a/tests/utils_tests/test_html.py
|
||||
+++ b/tests/utils_tests/test_html.py
|
||||
@@ -142,10 +142,10 @@ def test_strip_tags(self):
|
||||
Index: django-5.2.4/tests/utils_tests/test_html.py
|
||||
===================================================================
|
||||
--- django-5.2.4.orig/tests/utils_tests/test_html.py
|
||||
+++ django-5.2.4/tests/utils_tests/test_html.py
|
||||
@@ -144,10 +144,10 @@ class TestUtilsHtml(SimpleTestCase):
|
||||
("&gotcha&#;<>", "&gotcha&#;<>"),
|
||||
("<sc<!-- -->ript>test<<!-- -->/script>", "ript>test"),
|
||||
("<script>alert()</script>&h", "alert()h"),
|
||||
("<script>alert()</script>&h", "alert()&h;"),
|
||||
- ("><!" + ("&" * 16000) + "D", "><!" + ("&" * 16000) + "D"),
|
||||
+ ("><!" + ("&" * 16000) + "D", ">"),
|
||||
("X<<<<br>br>br>br>X", "XX"),
|
||||
|
||||
24
test_strip_tags_incomplete.patch
Normal file
24
test_strip_tags_incomplete.patch
Normal file
@@ -0,0 +1,24 @@
|
||||
From 5ca0f62213911a77dd4a62e843db7e420cc98b78 Mon Sep 17 00:00:00 2001
|
||||
From: Jacob Walls <jacobtylerwalls@gmail.com>
|
||||
Date: Thu, 11 Dec 2025 08:44:19 -0500
|
||||
Subject: [PATCH] [5.2.x] Refs #36499 -- Adjusted test_strip_tags following
|
||||
Python behavior change for incomplete entities.
|
||||
|
||||
Backport of 7b80b2186300620931009fd62c2969f108fe7a62 from main.
|
||||
---
|
||||
tests/utils_tests/test_html.py | 35 +++++++++++++++++++++++++++++-----
|
||||
1 file changed, 30 insertions(+), 5 deletions(-)
|
||||
|
||||
Index: django-5.2.4/tests/utils_tests/test_html.py
|
||||
===================================================================
|
||||
--- django-5.2.4.orig/tests/utils_tests/test_html.py
|
||||
+++ django-5.2.4/tests/utils_tests/test_html.py
|
||||
@@ -143,7 +144,7 @@ class TestUtilsHtml(SimpleTestCase):
|
||||
# https://bugs.python.org/issue20288
|
||||
("&gotcha&#;<>", "&gotcha&#;<>"),
|
||||
("<sc<!-- -->ript>test<<!-- -->/script>", "ript>test"),
|
||||
- ("<script>alert()</script>&h", "alert()h"),
|
||||
+ ("<script>alert()</script>&h", "alert()&h;"),
|
||||
("><!" + ("&" * 16000) + "D", "><!" + ("&" * 16000) + "D"),
|
||||
("X<<<<br>br>br>br>X", "XX"),
|
||||
("<" * 50 + "a>" * 50, ""),
|
||||
Reference in New Issue
Block a user