From cc13485c298cdf46fc1efdda51b9385a4d8010aa Mon Sep 17 00:00:00 2001
From: Natalia <124304+nessita@users.noreply.github.com>
Date: Tue, 3 Sep 2024 09:37:31 -0300
Subject: [PATCH 1/8] [5.0.x] Post-release version bump.
---
django/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/django/__init__.py b/django/__init__.py
index 6954a553447f..9bb0975f1a70 100644
--- a/django/__init__.py
+++ b/django/__init__.py
@@ -1,6 +1,6 @@
from django.utils.version import get_version
-VERSION = (5, 0, 9, "final", 0)
+VERSION = (5, 0, 10, "alpha", 0)
__version__ = get_version(VERSION)
From 901ec7a217d174b25ac008c9c385928a36f870d1 Mon Sep 17 00:00:00 2001
From: Natalia <124304+nessita@users.noreply.github.com>
Date: Tue, 3 Sep 2024 11:19:02 -0300
Subject: [PATCH 2/8] [5.0.x] Added CVE-2024-45230 and CVE-2024-45231 to
security archive.
Backport of aa5293068782dfa2d2173c75c8477f58a9989942 from main.
---
docs/releases/security.txt | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/docs/releases/security.txt b/docs/releases/security.txt
index 5d2c3900f508..c99953a81bf6 100644
--- a/docs/releases/security.txt
+++ b/docs/releases/security.txt
@@ -36,6 +36,28 @@ Issues under Django's security process
All security issues have been handled under versions of Django's security
process. These are listed below.
+September 3, 2024 - :cve:`2024-45231`
+-------------------------------------
+
+Potential user email enumeration via response status on password reset.
+`Full description
+`__
+
+* Django 5.1 :commit:`(patch) <3c733c78d6f8e50296d6e248968b6516c92a53ca>`
+* Django 5.0 :commit:`(patch) <96d84047715ea1715b4bd1594e46122b8a77b9e2>`
+* Django 4.2 :commit:`(patch) `
+
+September 3, 2024 - :cve:`2024-45230`
+-------------------------------------
+
+Potential denial-of-service vulnerability in ``django.utils.html.urlize()``.
+`Full description
+`__
+
+* Django 5.1 :commit:`(patch) <022ab0a75c76ab2ea31dfcc5f2cf5501e378d397>`
+* Django 5.0 :commit:`(patch) <813de2672bd7361e9a453ab62cd6e52f96b6525b>`
+* Django 4.2 :commit:`(patch) `
+
August 6, 2024 - :cve:`2024-42005`
----------------------------------
From 5064ddb4f9532cb2827744c052f277c39b74920a Mon Sep 17 00:00:00 2001
From: Tainara Palmeira
Date: Mon, 28 Oct 2024 14:46:20 +0100
Subject: [PATCH 3/8] [5.0.x] Refs #35844 -- Expanded compatibility for
expected error messages in command tests on Python 3.12.
Updated CommandTests.test_subparser_invalid_option and CommandDBOptionChoiceTests.test_invalid_choice_db_option to use assertRaisesRegex() for compatibility with modified error messages in Python 3.12, 3.13, and 3.14+..
Backport of fc22fdd34f1e55adde161f5f2dca8db90bbfce80 from main.
---
tests/user_commands/tests.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/tests/user_commands/tests.py b/tests/user_commands/tests.py
index 65e176620db1..2a1e904f3bda 100644
--- a/tests/user_commands/tests.py
+++ b/tests/user_commands/tests.py
@@ -400,8 +400,8 @@ def test_subparser_dest_required_args(self):
self.assertIn("bar", out.getvalue())
def test_subparser_invalid_option(self):
- msg = "invalid choice: 'test' (choose from 'foo')"
- with self.assertRaisesMessage(CommandError, msg):
+ msg = r"invalid choice: 'test' \(choose from '?foo'?\)"
+ with self.assertRaisesRegex(CommandError, msg):
management.call_command("subparser", "test", 12)
msg = "Error: the following arguments are required: subcommand"
with self.assertRaisesMessage(CommandError, msg):
From c8ce36bb7b5198382baedbdcd0074b3d8aa99c1f Mon Sep 17 00:00:00 2001
From: Mariusz Felisiak
Date: Fri, 11 Oct 2024 13:50:51 +0200
Subject: [PATCH 4/8] [5.0.x] Fixed docs build on Sphinx 8.1+.
Sphinx 8.1 added :cve: role, so there is no need to define it in Django:
- https://github.com/sphinx-doc/sphinx/pull/11781
This also changes used URL to the one used by Python and soonish to be
used by Sphinx itself:
- https://github.com/sphinx-doc/sphinx/pull/13006
Backport of 263f7319192b217c4e3b1eea0ea7809836392bbc from main.
---
docs/conf.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/docs/conf.py b/docs/conf.py
index 728e74e52c12..2ae4e5bd4511 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -12,6 +12,8 @@
import sys
from os.path import abspath, dirname, join
+from sphinx import version_info as sphinx_version
+
# Workaround for sphinx-build recursion limit overflow:
# pickle.dump(doctree, f, pickle.HIGHEST_PROTOCOL)
# RuntimeError: maximum recursion depth exceeded while pickling an object
@@ -133,13 +135,15 @@ def django_release():
extlinks = {
"bpo": ("https://bugs.python.org/issue?@action=redirect&bpo=%s", "bpo-%s"),
"commit": ("https://github.com/django/django/commit/%s", "%s"),
- "cve": ("https://nvd.nist.gov/vuln/detail/CVE-%s", "CVE-%s"),
"pypi": ("https://pypi.org/project/%s/", "%s"),
# A file or directory. GitHub redirects from blob to tree if needed.
"source": ("https://github.com/django/django/blob/main/%s", "%s"),
"ticket": ("https://code.djangoproject.com/ticket/%s", "#%s"),
}
+if sphinx_version < (8, 1):
+ extlinks["cve"] = ("https://www.cve.org/CVERecord?id=CVE-%s", "CVE-%s")
+
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
# language = None
From baf63eb0981e8127419a1f4fd98f3a533525ec44 Mon Sep 17 00:00:00 2001
From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com>
Date: Wed, 27 Nov 2024 14:30:12 +0100
Subject: [PATCH 5/8] [5.0.x] Added stub release notes and release date for
5.0.10, and 4.2.17.
Backport of 2544c1585473c1e82dab1274b52052744f97ca72 from main.
---
docs/releases/4.2.17.txt | 8 ++++++++
docs/releases/5.0.10.txt | 8 ++++++++
docs/releases/index.txt | 2 ++
3 files changed, 18 insertions(+)
create mode 100644 docs/releases/4.2.17.txt
create mode 100644 docs/releases/5.0.10.txt
diff --git a/docs/releases/4.2.17.txt b/docs/releases/4.2.17.txt
new file mode 100644
index 000000000000..5139d7034d4d
--- /dev/null
+++ b/docs/releases/4.2.17.txt
@@ -0,0 +1,8 @@
+===========================
+Django 4.2.17 release notes
+===========================
+
+*December 4, 2024*
+
+Django 4.2.17 fixes one security issue with severity "high" and one security
+issue with severity "moderate" in 4.2.16.
diff --git a/docs/releases/5.0.10.txt b/docs/releases/5.0.10.txt
new file mode 100644
index 000000000000..b06c3760381b
--- /dev/null
+++ b/docs/releases/5.0.10.txt
@@ -0,0 +1,8 @@
+===========================
+Django 5.0.10 release notes
+===========================
+
+*December 4, 2024*
+
+Django 5.0.10 fixes one security issue with severity "high" and one security
+issue with severity "moderate" in 5.0.9.
diff --git a/docs/releases/index.txt b/docs/releases/index.txt
index f4be3f453d50..8308705f3a17 100644
--- a/docs/releases/index.txt
+++ b/docs/releases/index.txt
@@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases.
.. toctree::
:maxdepth: 1
+ 5.0.10
5.0.9
5.0.8
5.0.7
@@ -42,6 +43,7 @@ versions of the documentation contain the release notes for any later releases.
.. toctree::
:maxdepth: 1
+ 4.2.17
4.2.16
4.2.15
4.2.14
From a5a89ea28cc550c1b29b03f9e14ef3c128ec1e84 Mon Sep 17 00:00:00 2001
From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com>
Date: Wed, 13 Nov 2024 15:06:23 +0100
Subject: [PATCH 6/8] [5.0.x] Fixed CVE-2024-53907 -- Mitigated potential DoS
in strip_tags().
Thanks to jiangniao for the report, and Shai Berger and Natalia Bidart
for the reviews.
---
django/utils/html.py | 10 ++++++++--
docs/releases/4.2.17.txt | 16 ++++++++++++++++
docs/releases/5.0.10.txt | 16 ++++++++++++++++
tests/utils_tests/test_html.py | 7 +++++++
4 files changed, 47 insertions(+), 2 deletions(-)
diff --git a/django/utils/html.py b/django/utils/html.py
index ac444cf8ee8a..d04e594d13a1 100644
--- a/django/utils/html.py
+++ b/django/utils/html.py
@@ -7,6 +7,7 @@
from html.parser import HTMLParser
from urllib.parse import parse_qsl, quote, unquote, urlencode, urlsplit, urlunsplit
+from django.core.exceptions import SuspiciousOperation
from django.utils.deprecation import RemovedInDjango60Warning
from django.utils.encoding import punycode
from django.utils.functional import Promise, cached_property, keep_lazy, keep_lazy_text
@@ -37,6 +38,7 @@
}
MAX_URL_LENGTH = 2048
+MAX_STRIP_TAGS_DEPTH = 50
@keep_lazy(SafeString)
@@ -202,15 +204,19 @@ def _strip_once(value):
@keep_lazy_text
def strip_tags(value):
"""Return the given HTML with all tags stripped."""
- # Note: in typical case this loop executes _strip_once once. Loop condition
- # is redundant, but helps to reduce number of executions of _strip_once.
value = str(value)
+ # Note: in typical case this loop executes _strip_once twice (the second
+ # execution does not remove any more tags).
+ strip_tags_depth = 0
while "<" in value and ">" in value:
+ if strip_tags_depth >= MAX_STRIP_TAGS_DEPTH:
+ raise SuspiciousOperation
new_value = _strip_once(value)
if value.count("<") == new_value.count("<"):
# _strip_once wasn't able to detect more tags.
break
value = new_value
+ strip_tags_depth += 1
return value
diff --git a/docs/releases/4.2.17.txt b/docs/releases/4.2.17.txt
index 5139d7034d4d..9db07f6da73d 100644
--- a/docs/releases/4.2.17.txt
+++ b/docs/releases/4.2.17.txt
@@ -6,3 +6,19 @@ Django 4.2.17 release notes
Django 4.2.17 fixes one security issue with severity "high" and one security
issue with severity "moderate" in 4.2.16.
+
+CVE-2024-53907: Denial-of-service possibility in ``strip_tags()``
+=================================================================
+
+:func:`~django.utils.html.strip_tags` would be extremely slow to evaluate
+certain inputs containing large sequences of nested incomplete HTML entities.
+The ``strip_tags()`` method is used to implement the corresponding
+:tfilter:`striptags` template filter, which was thus also vulnerable.
+
+``strip_tags()`` now has an upper limit of recursive calls to ``HTMLParser``
+before raising a :exc:`.SuspiciousOperation` exception.
+
+Remember that absolutely NO guarantee is provided about the results of
+``strip_tags()`` being HTML safe. So NEVER mark safe the result of a
+``strip_tags()`` call without escaping it first, for example with
+:func:`django.utils.html.escape`.
diff --git a/docs/releases/5.0.10.txt b/docs/releases/5.0.10.txt
index b06c3760381b..54569516a5e6 100644
--- a/docs/releases/5.0.10.txt
+++ b/docs/releases/5.0.10.txt
@@ -6,3 +6,19 @@ Django 5.0.10 release notes
Django 5.0.10 fixes one security issue with severity "high" and one security
issue with severity "moderate" in 5.0.9.
+
+CVE-2024-53907: Denial-of-service possibility in ``strip_tags()``
+=================================================================
+
+:func:`~django.utils.html.strip_tags` would be extremely slow to evaluate
+certain inputs containing large sequences of nested incomplete HTML entities.
+The ``strip_tags()`` method is used to implement the corresponding
+:tfilter:`striptags` template filter, which was thus also vulnerable.
+
+``strip_tags()`` now has an upper limit of recursive calls to ``HTMLParser``
+before raising a :exc:`.SuspiciousOperation` exception.
+
+Remember that absolutely NO guarantee is provided about the results of
+``strip_tags()`` being HTML safe. So NEVER mark safe the result of a
+``strip_tags()`` call without escaping it first, for example with
+:func:`django.utils.html.escape`.
diff --git a/tests/utils_tests/test_html.py b/tests/utils_tests/test_html.py
index 035585ec1016..e7efe0e5687b 100644
--- a/tests/utils_tests/test_html.py
+++ b/tests/utils_tests/test_html.py
@@ -1,6 +1,7 @@
import os
from datetime import datetime
+from django.core.exceptions import SuspiciousOperation
from django.core.serializers.json import DjangoJSONEncoder
from django.test import SimpleTestCase
from django.utils.deprecation import RemovedInDjango60Warning
@@ -123,12 +124,18 @@ def test_strip_tags(self):
("&h", "alert()h"),
(">br>br>br>X", "XX"),
+ ("<" * 50 + "a>" * 50, ""),
)
for value, output in items:
with self.subTest(value=value, output=output):
self.check_output(strip_tags, value, output)
self.check_output(strip_tags, lazystr(value), output)
+ def test_strip_tags_suspicious_operation(self):
+ value = "<" * 51 + "a>" * 51, ""
+ with self.assertRaises(SuspiciousOperation):
+ strip_tags(value)
+
def test_strip_tags_files(self):
# Test with more lengthy content (also catching performance regressions)
for filename in ("strip_tags1.html", "strip_tags2.txt"):
From ff08bb6c70aa45f83a5ef3bd0b601c7c9d1a7642 Mon Sep 17 00:00:00 2001
From: Simon Charette
Date: Fri, 8 Nov 2024 21:27:31 -0500
Subject: [PATCH 7/8] [5.0.x] Fixed CVE-2024-53908 -- Prevented SQL injections
in direct HasKeyLookup usage on Oracle.
Thanks Seokchan Yoon for the report, and Mariusz Felisiak and Sarah
Boyce for the reviews.
---
django/db/models/fields/json.py | 53 ++++++++++++++++++----------
docs/releases/4.2.17.txt | 9 +++++
docs/releases/5.0.10.txt | 9 +++++
tests/model_fields/test_jsonfield.py | 9 +++++
4 files changed, 62 insertions(+), 18 deletions(-)
diff --git a/django/db/models/fields/json.py b/django/db/models/fields/json.py
index b7cde157c4fa..b9c6ff1752b9 100644
--- a/django/db/models/fields/json.py
+++ b/django/db/models/fields/json.py
@@ -216,20 +216,18 @@ def compile_json_path_final_key(self, key_transform):
# Compile the final key without interpreting ints as array elements.
return ".%s" % json.dumps(key_transform)
- def as_sql(self, compiler, connection, template=None):
+ def _as_sql_parts(self, compiler, connection):
# Process JSON path from the left-hand side.
if isinstance(self.lhs, KeyTransform):
- lhs, lhs_params, lhs_key_transforms = self.lhs.preprocess_lhs(
+ lhs_sql, lhs_params, lhs_key_transforms = self.lhs.preprocess_lhs(
compiler, connection
)
lhs_json_path = compile_json_path(lhs_key_transforms)
else:
- lhs, lhs_params = self.process_lhs(compiler, connection)
+ lhs_sql, lhs_params = self.process_lhs(compiler, connection)
lhs_json_path = "$"
- sql = template % lhs
# Process JSON path from the right-hand side.
rhs = self.rhs
- rhs_params = []
if not isinstance(rhs, (list, tuple)):
rhs = [rhs]
for key in rhs:
@@ -240,24 +238,43 @@ def as_sql(self, compiler, connection, template=None):
*rhs_key_transforms, final_key = rhs_key_transforms
rhs_json_path = compile_json_path(rhs_key_transforms, include_root=False)
rhs_json_path += self.compile_json_path_final_key(final_key)
- rhs_params.append(lhs_json_path + rhs_json_path)
+ yield lhs_sql, lhs_params, lhs_json_path + rhs_json_path
+
+ def _combine_sql_parts(self, parts):
# Add condition for each key.
if self.logical_operator:
- sql = "(%s)" % self.logical_operator.join([sql] * len(rhs_params))
- return sql, tuple(lhs_params) + tuple(rhs_params)
+ return "(%s)" % self.logical_operator.join(parts)
+ return "".join(parts)
+
+ def as_sql(self, compiler, connection, template=None):
+ sql_parts = []
+ params = []
+ for lhs_sql, lhs_params, rhs_json_path in self._as_sql_parts(
+ compiler, connection
+ ):
+ sql_parts.append(template % (lhs_sql, "%s"))
+ params.extend(lhs_params + [rhs_json_path])
+ return self._combine_sql_parts(sql_parts), tuple(params)
def as_mysql(self, compiler, connection):
return self.as_sql(
- compiler, connection, template="JSON_CONTAINS_PATH(%s, 'one', %%s)"
+ compiler, connection, template="JSON_CONTAINS_PATH(%s, 'one', %s)"
)
def as_oracle(self, compiler, connection):
- sql, params = self.as_sql(
- compiler, connection, template="JSON_EXISTS(%s, '%%s')"
- )
- # Add paths directly into SQL because path expressions cannot be passed
- # as bind variables on Oracle.
- return sql % tuple(params), []
+ template = "JSON_EXISTS(%s, '%s')"
+ sql_parts = []
+ params = []
+ for lhs_sql, lhs_params, rhs_json_path in self._as_sql_parts(
+ compiler, connection
+ ):
+ # Add right-hand-side directly into SQL because it cannot be passed
+ # as bind variables to JSON_EXISTS. It might result in invalid
+ # queries but it is assumed that it cannot be evaded because the
+ # path is JSON serialized.
+ sql_parts.append(template % (lhs_sql, rhs_json_path))
+ params.extend(lhs_params)
+ return self._combine_sql_parts(sql_parts), tuple(params)
def as_postgresql(self, compiler, connection):
if isinstance(self.rhs, KeyTransform):
@@ -269,7 +286,7 @@ def as_postgresql(self, compiler, connection):
def as_sqlite(self, compiler, connection):
return self.as_sql(
- compiler, connection, template="JSON_TYPE(%s, %%s) IS NOT NULL"
+ compiler, connection, template="JSON_TYPE(%s, %s) IS NOT NULL"
)
@@ -467,9 +484,9 @@ def as_oracle(self, compiler, connection):
return "(NOT %s OR %s IS NULL)" % (sql, lhs), tuple(params) + tuple(lhs_params)
def as_sqlite(self, compiler, connection):
- template = "JSON_TYPE(%s, %%s) IS NULL"
+ template = "JSON_TYPE(%s, %s) IS NULL"
if not self.rhs:
- template = "JSON_TYPE(%s, %%s) IS NOT NULL"
+ template = "JSON_TYPE(%s, %s) IS NOT NULL"
return HasKeyOrArrayIndex(self.lhs.lhs, self.lhs.key_name).as_sql(
compiler,
connection,
diff --git a/docs/releases/4.2.17.txt b/docs/releases/4.2.17.txt
index 9db07f6da73d..9a6aee3db6ef 100644
--- a/docs/releases/4.2.17.txt
+++ b/docs/releases/4.2.17.txt
@@ -22,3 +22,12 @@ Remember that absolutely NO guarantee is provided about the results of
``strip_tags()`` being HTML safe. So NEVER mark safe the result of a
``strip_tags()`` call without escaping it first, for example with
:func:`django.utils.html.escape`.
+
+CVE-2024-53908: Potential SQL injection via ``HasKey(lhs, rhs)`` on Oracle
+==========================================================================
+
+Direct usage of the ``django.db.models.fields.json.HasKey`` lookup on Oracle
+was subject to SQL injection if untrusted data was used as a ``lhs`` value.
+
+Applications that use the :lookup:`has_key ` lookup through
+the ``__`` syntax are unaffected.
diff --git a/docs/releases/5.0.10.txt b/docs/releases/5.0.10.txt
index 54569516a5e6..ae1fbf99e40a 100644
--- a/docs/releases/5.0.10.txt
+++ b/docs/releases/5.0.10.txt
@@ -22,3 +22,12 @@ Remember that absolutely NO guarantee is provided about the results of
``strip_tags()`` being HTML safe. So NEVER mark safe the result of a
``strip_tags()`` call without escaping it first, for example with
:func:`django.utils.html.escape`.
+
+CVE-2024-53908: Potential SQL injection via ``HasKey(lhs, rhs)`` on Oracle
+==========================================================================
+
+Direct usage of the ``django.db.models.fields.json.HasKey`` lookup on Oracle
+was subject to SQL injection if untrusted data was used as a ``lhs`` value.
+
+Applications that use the :lookup:`has_key ` lookup through
+the ``__`` syntax are unaffected.
diff --git a/tests/model_fields/test_jsonfield.py b/tests/model_fields/test_jsonfield.py
index 63e06b99cb50..eb129370fbdc 100644
--- a/tests/model_fields/test_jsonfield.py
+++ b/tests/model_fields/test_jsonfield.py
@@ -29,6 +29,7 @@
from django.db.models.expressions import RawSQL
from django.db.models.fields.json import (
KT,
+ HasKey,
KeyTextTransform,
KeyTransform,
KeyTransformFactory,
@@ -607,6 +608,14 @@ def test_has_key_deep(self):
[expected],
)
+ def test_has_key_literal_lookup(self):
+ self.assertSequenceEqual(
+ NullableJSONModel.objects.filter(
+ HasKey(Value({"foo": "bar"}, JSONField()), "foo")
+ ).order_by("id"),
+ self.objs,
+ )
+
def test_has_key_list(self):
obj = NullableJSONModel.objects.create(value=[{"a": 1}, {"b": "x"}])
tests = [
From 3b3a5f4efbf93692557b7f473519bd0ad8f04c6a Mon Sep 17 00:00:00 2001
From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com>
Date: Wed, 4 Dec 2024 14:27:16 +0100
Subject: [PATCH 8/8] [5.0.x] Bumped version for 5.0.10 release.
---
django/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/django/__init__.py b/django/__init__.py
index 9bb0975f1a70..45efb4d5d76a 100644
--- a/django/__init__.py
+++ b/django/__init__.py
@@ -1,6 +1,6 @@
from django.utils.version import get_version
-VERSION = (5, 0, 10, "alpha", 0)
+VERSION = (5, 0, 10, "final", 0)
__version__ = get_version(VERSION)