Bug#1003659: bullseye-pu: package python-django/2:2.2.26-1~deb11u1
Package: release.debian.org
Severity: normal
Tags: bullseye
User: release.debian.org@packages.debian.org
Usertags: pu
Dear stable release managers,
Please consider python-django (2:2.2.26-1~deb11u1) for bullseye:
python-django (2:2.2.26-1~deb11u1) bullseye; urgency=medium
.
* New upstream security release:
.
- CVE-2021-45115: Denial-of-service possibility in
UserAttributeSimilarityValidator
.
UserAttributeSimilarityValidator incurred significant overhead evaluating
submitted password that were artificially large in relative to the
comparison values. On the assumption that access to user registration was
unrestricted this provided a potential vector for a denial-of-service
attack.
.
In order to mitigate this issue, relatively long values are now ignored
by UserAttributeSimilarityValidator.
.
- CVE-2021-45116: Potential information disclosure in dictsort template
filter
.
Due to leveraging the Django Template Language's variable resolution
logic, the dictsort template filter was potentially vulnerable to
information disclosure or unintended method calls, if passed a
suitably crafted key.
.
In order to avoid this possibility, dictsort now works with a
restricted resolution logic, that will not call methods, nor allow
indexing on dictionaries.
.
- CVE-2021-45452: Potential directory-traversal via Storage.save()
.
Storage.save() allowed directory-traversal if directly passed suitably
crafted file names.
.
See <https://www.djangoproject.com/weblog/2022/jan/04/security-releases/>
for more information. (Closes: #1003113)
.
* Fix a traceback around the handling of RequestSite/get_current_site() due
to a circular import by backporting commit 78163d1a from upstream. Thanks
to Raphaël Hertzog for the report. (Closes: #1003478)
The full diff is attached.
Regards,
--
,''`.
: :' : Chris Lamb
`. `'` lamby@debian.org / chris-lamb.co.uk
`-
diff --git a/debian/changelog b/debian/changelog
index 68a035164..97e3e9247 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,45 @@
+python-django (2:2.2.26-1~deb11u1) bullseye; urgency=medium
+
+ * New upstream security release:
+
+ - CVE-2021-45115: Denial-of-service possibility in
+ UserAttributeSimilarityValidator
+
+ UserAttributeSimilarityValidator incurred significant overhead evaluating
+ submitted password that were artificially large in relative to the
+ comparison values. On the assumption that access to user registration was
+ unrestricted this provided a potential vector for a denial-of-service
+ attack.
+
+ In order to mitigate this issue, relatively long values are now ignored
+ by UserAttributeSimilarityValidator.
+
+ - CVE-2021-45116: Potential information disclosure in dictsort template
+ filter
+
+ Due to leveraging the Django Template Language's variable resolution
+ logic, the dictsort template filter was potentially vulnerable to
+ information disclosure or unintended method calls, if passed a
+ suitably crafted key.
+
+ In order to avoid this possibility, dictsort now works with a
+ restricted resolution logic, that will not call methods, nor allow
+ indexing on dictionaries.
+
+ - CVE-2021-45452: Potential directory-traversal via Storage.save()
+
+ Storage.save() allowed directory-traversal if directly passed suitably
+ crafted file names.
+
+ See <https://www.djangoproject.com/weblog/2022/jan/04/security-releases/>
+ for more information. (Closes: #1003113)
+
+ * Fix a traceback around the handling of RequestSite/get_current_site() due
+ to a circular import by backporting commit 78163d1a from upstream. Thanks
+ to Raphaël Hertzog for the report. (Closes: #1003478)
+
+ -- Chris Lamb <lamby@debian.org> Thu, 13 Jan 2022 11:11:29 +0000
+
python-django (2:2.2.25-1~deb11u1) bullseye; urgency=medium
* New upstream security release:
diff --git a/Django.egg-info/PKG-INFO b/Django.egg-info/PKG-INFO
index ecc68c78d..51d529c0e 100644
--- a/Django.egg-info/PKG-INFO
+++ b/Django.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: Django
-Version: 2.2.25
+Version: 2.2.26
Summary: A high-level Python Web framework that encourages rapid development and clean, pragmatic design.
Home-page: https://www.djangoproject.com/
Author: Django Software Foundation
@@ -10,51 +10,6 @@ Project-URL: Documentation, https://docs.djangoproject.com/
Project-URL: Funding, https://www.djangoproject.com/fundraising/
Project-URL: Source, https://github.com/django/django
Project-URL: Tracker, https://code.djangoproject.com/
-Description: Django is a high-level Python Web framework that encourages rapid development
- and clean, pragmatic design. Thanks for checking it out.
-
- All documentation is in the "``docs``" directory and online at
- https://docs.djangoproject.com/en/stable/. If you're just getting started,
- here's how we recommend you read the docs:
-
- * First, read ``docs/intro/install.txt`` for instructions on installing Django.
-
- * Next, work through the tutorials in order (``docs/intro/tutorial01.txt``,
- ``docs/intro/tutorial02.txt``, etc.).
-
- * If you want to set up an actual deployment server, read
- ``docs/howto/deployment/index.txt`` for instructions.
-
- * You'll probably want to read through the topical guides (in ``docs/topics``)
- next; from there you can jump to the HOWTOs (in ``docs/howto``) for specific
- problems, and check out the reference (``docs/ref``) for gory details.
-
- * See ``docs/README`` for instructions on building an HTML version of the docs.
-
- Docs are updated rigorously. If you find any problems in the docs, or think
- they should be clarified in any way, please take 30 seconds to fill out a
- ticket here: https://code.djangoproject.com/newticket
-
- To get more help:
-
- * Join the ``#django`` channel on ``irc.libera.chat``. Lots of helpful people
- out there. See https://en.wikipedia.org/wiki/Wikipedia:IRC/Tutorial if you're
- new to IRC.
-
- * Join the django-users mailing list, or read the archives, at
- https://groups.google.com/group/django-users.
-
- To contribute to Django:
-
- * Check out https://docs.djangoproject.com/en/dev/internals/contributing/ for
- information about getting involved.
-
- To run Django's test suite:
-
- * Follow the instructions in the "Unit tests" section of
- ``docs/internals/contributing/writing-code/unit-tests.txt``, published online at
- https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/unit-tests/#running-the-unit-tests
-
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
@@ -76,5 +31,53 @@ Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.5
-Provides-Extra: argon2
Provides-Extra: bcrypt
+Provides-Extra: argon2
+License-File: LICENSE
+
+Django is a high-level Python Web framework that encourages rapid development
+and clean, pragmatic design. Thanks for checking it out.
+
+All documentation is in the "``docs``" directory and online at
+https://docs.djangoproject.com/en/stable/. If you're just getting started,
+here's how we recommend you read the docs:
+
+* First, read ``docs/intro/install.txt`` for instructions on installing Django.
+
+* Next, work through the tutorials in order (``docs/intro/tutorial01.txt``,
+ ``docs/intro/tutorial02.txt``, etc.).
+
+* If you want to set up an actual deployment server, read
+ ``docs/howto/deployment/index.txt`` for instructions.
+
+* You'll probably want to read through the topical guides (in ``docs/topics``)
+ next; from there you can jump to the HOWTOs (in ``docs/howto``) for specific
+ problems, and check out the reference (``docs/ref``) for gory details.
+
+* See ``docs/README`` for instructions on building an HTML version of the docs.
+
+Docs are updated rigorously. If you find any problems in the docs, or think
+they should be clarified in any way, please take 30 seconds to fill out a
+ticket here: https://code.djangoproject.com/newticket
+
+To get more help:
+
+* Join the ``#django`` channel on ``irc.libera.chat``. Lots of helpful people
+ out there. See https://en.wikipedia.org/wiki/Wikipedia:IRC/Tutorial if you're
+ new to IRC.
+
+* Join the django-users mailing list, or read the archives, at
+ https://groups.google.com/group/django-users.
+
+To contribute to Django:
+
+* Check out https://docs.djangoproject.com/en/dev/internals/contributing/ for
+ information about getting involved.
+
+To run Django's test suite:
+
+* Follow the instructions in the "Unit tests" section of
+ ``docs/internals/contributing/writing-code/unit-tests.txt``, published online at
+ https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/unit-tests/#running-the-unit-tests
+
+
diff --git a/Django.egg-info/SOURCES.txt b/Django.egg-info/SOURCES.txt
index 7c210f1d4..7e5807e07 100644
--- a/Django.egg-info/SOURCES.txt
+++ b/Django.egg-info/SOURCES.txt
@@ -3836,6 +3836,7 @@ docs/releases/2.2.22.txt
docs/releases/2.2.23.txt
docs/releases/2.2.24.txt
docs/releases/2.2.25.txt
+docs/releases/2.2.26.txt
docs/releases/2.2.3.txt
docs/releases/2.2.4.txt
docs/releases/2.2.5.txt
diff --git a/PKG-INFO b/PKG-INFO
index ecc68c78d..51d529c0e 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: Django
-Version: 2.2.25
+Version: 2.2.26
Summary: A high-level Python Web framework that encourages rapid development and clean, pragmatic design.
Home-page: https://www.djangoproject.com/
Author: Django Software Foundation
@@ -10,51 +10,6 @@ Project-URL: Documentation, https://docs.djangoproject.com/
Project-URL: Funding, https://www.djangoproject.com/fundraising/
Project-URL: Source, https://github.com/django/django
Project-URL: Tracker, https://code.djangoproject.com/
-Description: Django is a high-level Python Web framework that encourages rapid development
- and clean, pragmatic design. Thanks for checking it out.
-
- All documentation is in the "``docs``" directory and online at
- https://docs.djangoproject.com/en/stable/. If you're just getting started,
- here's how we recommend you read the docs:
-
- * First, read ``docs/intro/install.txt`` for instructions on installing Django.
-
- * Next, work through the tutorials in order (``docs/intro/tutorial01.txt``,
- ``docs/intro/tutorial02.txt``, etc.).
-
- * If you want to set up an actual deployment server, read
- ``docs/howto/deployment/index.txt`` for instructions.
-
- * You'll probably want to read through the topical guides (in ``docs/topics``)
- next; from there you can jump to the HOWTOs (in ``docs/howto``) for specific
- problems, and check out the reference (``docs/ref``) for gory details.
-
- * See ``docs/README`` for instructions on building an HTML version of the docs.
-
- Docs are updated rigorously. If you find any problems in the docs, or think
- they should be clarified in any way, please take 30 seconds to fill out a
- ticket here: https://code.djangoproject.com/newticket
-
- To get more help:
-
- * Join the ``#django`` channel on ``irc.libera.chat``. Lots of helpful people
- out there. See https://en.wikipedia.org/wiki/Wikipedia:IRC/Tutorial if you're
- new to IRC.
-
- * Join the django-users mailing list, or read the archives, at
- https://groups.google.com/group/django-users.
-
- To contribute to Django:
-
- * Check out https://docs.djangoproject.com/en/dev/internals/contributing/ for
- information about getting involved.
-
- To run Django's test suite:
-
- * Follow the instructions in the "Unit tests" section of
- ``docs/internals/contributing/writing-code/unit-tests.txt``, published online at
- https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/unit-tests/#running-the-unit-tests
-
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
@@ -76,5 +31,53 @@ Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.5
-Provides-Extra: argon2
Provides-Extra: bcrypt
+Provides-Extra: argon2
+License-File: LICENSE
+
+Django is a high-level Python Web framework that encourages rapid development
+and clean, pragmatic design. Thanks for checking it out.
+
+All documentation is in the "``docs``" directory and online at
+https://docs.djangoproject.com/en/stable/. If you're just getting started,
+here's how we recommend you read the docs:
+
+* First, read ``docs/intro/install.txt`` for instructions on installing Django.
+
+* Next, work through the tutorials in order (``docs/intro/tutorial01.txt``,
+ ``docs/intro/tutorial02.txt``, etc.).
+
+* If you want to set up an actual deployment server, read
+ ``docs/howto/deployment/index.txt`` for instructions.
+
+* You'll probably want to read through the topical guides (in ``docs/topics``)
+ next; from there you can jump to the HOWTOs (in ``docs/howto``) for specific
+ problems, and check out the reference (``docs/ref``) for gory details.
+
+* See ``docs/README`` for instructions on building an HTML version of the docs.
+
+Docs are updated rigorously. If you find any problems in the docs, or think
+they should be clarified in any way, please take 30 seconds to fill out a
+ticket here: https://code.djangoproject.com/newticket
+
+To get more help:
+
+* Join the ``#django`` channel on ``irc.libera.chat``. Lots of helpful people
+ out there. See https://en.wikipedia.org/wiki/Wikipedia:IRC/Tutorial if you're
+ new to IRC.
+
+* Join the django-users mailing list, or read the archives, at
+ https://groups.google.com/group/django-users.
+
+To contribute to Django:
+
+* Check out https://docs.djangoproject.com/en/dev/internals/contributing/ for
+ information about getting involved.
+
+To run Django's test suite:
+
+* Follow the instructions in the "Unit tests" section of
+ ``docs/internals/contributing/writing-code/unit-tests.txt``, published online at
+ https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/unit-tests/#running-the-unit-tests
+
+
diff --git a/debian/patches/0006-Moved-RequestSite-import-to-the-toplevel.patch b/debian/patches/0006-Moved-RequestSite-import-to-the-toplevel.patch
new file mode 100644
index 000000000..7e7cf1360
--- /dev/null
+++ b/debian/patches/0006-Moved-RequestSite-import-to-the-toplevel.patch
@@ -0,0 +1,34 @@
+From: Claude Paroz <claude@2xlibre.net>
+Date: Thu, 11 Nov 2021 08:22:04 +0100
+Subject: Moved RequestSite import to the toplevel.
+
+Via https://github.com/django/django/commit/78163d1ac4407d59bfc5fdf1f84f2dbbb2ed3443
+---
+ django/contrib/sites/shortcuts.py | 7 ++++---
+ 1 file changed, 4 insertions(+), 3 deletions(-)
+
+diff --git a/django/contrib/sites/shortcuts.py b/django/contrib/sites/shortcuts.py
+index 1a2ee5c384e1..1131dba1eaa7 100644
+--- a/django/contrib/sites/shortcuts.py
++++ b/django/contrib/sites/shortcuts.py
+@@ -1,16 +1,17 @@
+ from django.apps import apps
+
++from .requests import RequestSite
++
+
+ def get_current_site(request):
+ """
+ Check if contrib.sites is installed and return either the current
+ ``Site`` object or a ``RequestSite`` object based on the request.
+ """
+- # Imports are inside the function because its point is to avoid importing
+- # the Site models when django.contrib.sites isn't installed.
++ # Import is inside the function because its point is to avoid importing the
++ # Site models when django.contrib.sites isn't installed.
+ if apps.is_installed('django.contrib.sites'):
+ from .models import Site
+ return Site.objects.get_current(request)
+ else:
+- from .requests import RequestSite
+ return RequestSite(request)
diff --git a/debian/patches/series b/debian/patches/series
index 75da92298..6bbbe49ea 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -3,3 +3,4 @@
0004-Use-locally-installed-documentation-sources.patch
0004-Set-the-default-shebang-to-new-projects-to-use-Pytho.patch
0005-Use-usr-bin-env-python3-shebang-for-django-admin.py.patch
+0006-Moved-RequestSite-import-to-the-toplevel.patch
diff --git a/django/__init__.py b/django/__init__.py
index 4fbd3665a..1b15c8d9d 100644
--- a/django/__init__.py
+++ b/django/__init__.py
@@ -1,6 +1,6 @@
from django.utils.version import get_version
-VERSION = (2, 2, 25, 'final', 0)
+VERSION = (2, 2, 26, 'final', 0)
__version__ = get_version(VERSION)
diff --git a/django/contrib/auth/password_validation.py b/django/contrib/auth/password_validation.py
index 948ded6db..a80214ded 100644
--- a/django/contrib/auth/password_validation.py
+++ b/django/contrib/auth/password_validation.py
@@ -115,6 +115,36 @@ class MinimumLengthValidator:
) % {'min_length': self.min_length}
+def exceeds_maximum_length_ratio(password, max_similarity, value):
+ """
+ Test that value is within a reasonable range of password.
+
+ The following ratio calculations are based on testing SequenceMatcher like
+ this:
+
+ for i in range(0,6):
+ print(10**i, SequenceMatcher(a='A', b='A'*(10**i)).quick_ratio())
+
+ which yields:
+
+ 1 1.0
+ 10 0.18181818181818182
+ 100 0.019801980198019802
+ 1000 0.001998001998001998
+ 10000 0.00019998000199980003
+ 100000 1.999980000199998e-05
+
+ This means a length_ratio of 10 should never yield a similarity higher than
+ 0.2, for 100 this is down to 0.02 and for 1000 it is 0.002. This can be
+ calculated via 2 / length_ratio. As a result we avoid the potentially
+ expensive sequence matching.
+ """
+ pwd_len = len(password)
+ length_bound_similarity = max_similarity / 2 * pwd_len
+ value_len = len(value)
+ return pwd_len >= 10 * value_len and value_len < length_bound_similarity
+
+
class UserAttributeSimilarityValidator:
"""
Validate whether the password is sufficiently different from the user's
@@ -130,19 +160,25 @@ class UserAttributeSimilarityValidator:
def __init__(self, user_attributes=DEFAULT_USER_ATTRIBUTES, max_similarity=0.7):
self.user_attributes = user_attributes
+ if max_similarity < 0.1:
+ raise ValueError('max_similarity must be at least 0.1')
self.max_similarity = max_similarity
def validate(self, password, user=None):
if not user:
return
+ password = password.lower()
for attribute_name in self.user_attributes:
value = getattr(user, attribute_name, None)
if not value or not isinstance(value, str):
continue
- value_parts = re.split(r'\W+', value) + [value]
+ value_lower = value.lower()
+ value_parts = re.split(r'\W+', value_lower) + [value_lower]
for value_part in value_parts:
- if SequenceMatcher(a=password.lower(), b=value_part.lower()).quick_ratio() >= self.max_similarity:
+ if exceeds_maximum_length_ratio(password, self.max_similarity, value_part):
+ continue
+ if SequenceMatcher(a=password, b=value_part).quick_ratio() >= self.max_similarity:
try:
verbose_name = str(user._meta.get_field(attribute_name).verbose_name)
except FieldDoesNotExist:
diff --git a/django/core/files/storage.py b/django/core/files/storage.py
index 89faa626e..ea5bbc82d 100644
--- a/django/core/files/storage.py
+++ b/django/core/files/storage.py
@@ -51,7 +51,10 @@ class Storage:
content = File(content, name)
name = self.get_available_name(name, max_length=max_length)
- return self._save(name, content)
+ name = self._save(name, content)
+ # Ensure that the name returned from the storage system is still valid.
+ validate_file_name(name, allow_relative_path=True)
+ return name
# These methods are part of the public API, with default implementations.
@@ -67,6 +70,7 @@ class Storage:
Return a filename that's free on the target storage system and
available for new content to be written to.
"""
+ name = str(name).replace('\\', '/')
dir_name, file_name = os.path.split(name)
if '..' in pathlib.PurePath(dir_name).parts:
raise SuspiciousFileOperation("Detected path traversal attempt in '%s'" % dir_name)
@@ -101,6 +105,7 @@ class Storage:
Validate the filename by calling get_valid_name() and return a filename
to be passed to the save() method.
"""
+ filename = str(filename).replace('\\', '/')
# `filename` may include a path as returned by FileField.upload_to.
dirname, filename = os.path.split(filename)
if '..' in pathlib.PurePath(dirname).parts:
@@ -296,6 +301,8 @@ class FileSystemStorage(Storage):
if self.file_permissions_mode is not None:
os.chmod(full_path, self.file_permissions_mode)
+ # Ensure the saved path is always relative to the storage root.
+ name = os.path.relpath(full_path, self.location)
# Store filenames with forward slashes, even on Windows.
return name.replace('\\', '/')
diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
index f82c08348..a1d77f5e6 100644
--- a/django/template/defaultfilters.py
+++ b/django/template/defaultfilters.py
@@ -22,7 +22,7 @@ from django.utils.text import (
from django.utils.timesince import timesince, timeuntil
from django.utils.translation import gettext, ngettext
-from .base import Variable, VariableDoesNotExist
+from .base import VARIABLE_ATTRIBUTE_SEPARATOR
from .library import Library
register = Library()
@@ -465,7 +465,7 @@ def striptags(value):
def _property_resolver(arg):
"""
When arg is convertible to float, behave like operator.itemgetter(arg)
- Otherwise, behave like Variable(arg).resolve
+ Otherwise, chain __getitem__() and getattr().
>>> _property_resolver(1)('abc')
'b'
@@ -483,7 +483,19 @@ def _property_resolver(arg):
try:
float(arg)
except ValueError:
- return Variable(arg).resolve
+ if VARIABLE_ATTRIBUTE_SEPARATOR + '_' in arg or arg[0] == '_':
+ raise AttributeError('Access to private variables is forbidden.')
+ parts = arg.split(VARIABLE_ATTRIBUTE_SEPARATOR)
+
+ def resolve(value):
+ for part in parts:
+ try:
+ value = value[part]
+ except (AttributeError, IndexError, KeyError, TypeError, ValueError):
+ value = getattr(value, part)
+ return value
+
+ return resolve
else:
return itemgetter(arg)
@@ -496,7 +508,7 @@ def dictsort(value, arg):
"""
try:
return sorted(value, key=_property_resolver(arg))
- except (TypeError, VariableDoesNotExist):
+ except (AttributeError, TypeError):
return ''
@@ -508,7 +520,7 @@ def dictsortreversed(value, arg):
"""
try:
return sorted(value, key=_property_resolver(arg), reverse=True)
- except (TypeError, VariableDoesNotExist):
+ except (AttributeError, TypeError):
return ''
diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt
index 65a162e3b..bc24308ba 100644
--- a/docs/ref/templates/builtins.txt
+++ b/docs/ref/templates/builtins.txt
@@ -1575,6 +1575,13 @@ produce empty output::
{{ values|dictsort:"0" }}
+Ordering by elements at specified index is not supported on dictionaries.
+
+.. versionchanged:: 2.2.26
+
+ In older versions, ordering elements at specified index was supported on
+ dictionaries.
+
.. templatefilter:: dictsortreversed
``dictsortreversed``
diff --git a/docs/ref/urls.txt b/docs/ref/urls.txt
index 1527a3472..36bc1a7de 100644
--- a/docs/ref/urls.txt
+++ b/docs/ref/urls.txt
@@ -72,9 +72,18 @@ groups from the regular expression are passed to the view -- as named arguments
if the groups are named, and as positional arguments otherwise. The values are
passed as strings, without any type conversion.
+When a ``route`` ends with ``$`` the whole requested URL, matching against
+:attr:`~django.http.HttpRequest.path_info`, must match the regular expression
+pattern (:py:func:`re.fullmatch` is used).
+
The ``view``, ``kwargs`` and ``name`` arguments are the same as for
:func:`~django.urls.path()`.
+.. versionchanged:: 2.2.25
+
+ In older versions, a full-match wasn't required for a ``route`` which ends
+ with ``$``.
+
``include()``
=============
diff --git a/docs/releases/2.2.26.txt b/docs/releases/2.2.26.txt
new file mode 100644
index 000000000..7fbdc0208
--- /dev/null
+++ b/docs/releases/2.2.26.txt
@@ -0,0 +1,47 @@
+===========================
+Django 2.2.26 release notes
+===========================
+
+*January 4, 2022*
+
+Django 2.2.26 fixes one security issue with severity "medium" and two security
+issues with severity "low" in 2.2.25.
+
+CVE-2021-45115: Denial-of-service possibility in ``UserAttributeSimilarityValidator``
+=====================================================================================
+
+:class:`.UserAttributeSimilarityValidator` incurred significant overhead
+evaluating submitted password that were artificially large in relative to the
+comparison values. On the assumption that access to user registration was
+unrestricted this provided a potential vector for a denial-of-service attack.
+
+In order to mitigate this issue, relatively long values are now ignored by
+``UserAttributeSimilarityValidator``.
+
+This issue has severity "medium" according to the :ref:`Django security policy
+<security-disclosure>`.
+
+CVE-2021-45116: Potential information disclosure in ``dictsort`` template filter
+================================================================================
+
+Due to leveraging the Django Template Language's variable resolution logic, the
+:tfilter:`dictsort` template filter was potentially vulnerable to information
+disclosure or unintended method calls, if passed a suitably crafted key.
+
+In order to avoid this possibility, ``dictsort`` now works with a restricted
+resolution logic, that will not call methods, nor allow indexing on
+dictionaries.
+
+As a reminder, all untrusted user input should be validated before use.
+
+This issue has severity "low" according to the :ref:`Django security policy
+<security-disclosure>`.
+
+CVE-2021-45452: Potential directory-traversal via ``Storage.save()``
+====================================================================
+
+``Storage.save()`` allowed directory-traversal if directly passed suitably
+crafted file names.
+
+This issue has severity "low" according to the :ref:`Django security policy
+<security-disclosure>`.
diff --git a/docs/releases/index.txt b/docs/releases/index.txt
index e62548254..d7ceffca1 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
+ 2.2.26
2.2.25
2.2.24
2.2.23
diff --git a/docs/releases/security.txt b/docs/releases/security.txt
index 4d9096856..87dd512eb 100644
--- a/docs/releases/security.txt
+++ b/docs/releases/security.txt
@@ -1230,3 +1230,17 @@ Versions affected
* Django 3.2 :commit:`(patch) <9f75e2e562fa0c0482f3dde6fc7399a9070b4a3d>`
* Django 3.1 :commit:`(patch) <203d4ab9ebcd72fc4d6eb7398e66ed9e474e118e>`
* Django 2.2 :commit:`(patch) <f27c38ab5d90f68c9dd60cabef248a570c0be8fc>`
+
+December 7, 2021 - :cve:`2021-44420`
+------------------------------------
+
+Potential bypass of an upstream access control based on URL paths. `Full
+description
+<https://www.djangoproject.com/weblog/2021/dec/07/security-releases/>`__
+
+Versions affected
+~~~~~~~~~~~~~~~~~
+
+* Django 3.2 :commit:`(patch) <333c65603032c377e682cdbd7388657a5463a05a>`
+* Django 3.1 :commit:`(patch) <22bd17488159601bf0741b70ae7932bffea8eced>`
+* Django 2.2 :commit:`(patch) <7cf7d74e8a754446eeb85cacf2fef1247e0cb6d7>`
diff --git a/docs/topics/auth/passwords.txt b/docs/topics/auth/passwords.txt
index bcf20a976..c509a3a52 100644
--- a/docs/topics/auth/passwords.txt
+++ b/docs/topics/auth/passwords.txt
@@ -522,10 +522,16 @@ Django includes four validators:
is used: ``'username', 'first_name', 'last_name', 'email'``.
Attributes that don't exist are ignored.
- The minimum similarity of a rejected password can be set on a scale of 0 to
- 1 with the ``max_similarity`` parameter. A setting of 0 rejects all
- passwords, whereas a setting of 1 rejects only passwords that are identical
- to an attribute's value.
+ The maximum allowed similarity of passwords can be set on a scale of 0.1
+ to 1.0 with the ``max_similarity`` parameter. This is compared to the
+ result of :meth:`difflib.SequenceMatcher.quick_ratio`. A value of 0.1
+ rejects passwords unless they are substantially different from the
+ ``user_attributes``, whereas a value of 1.0 rejects only passwords that are
+ identical to an attribute's value.
+
+ .. versionchanged:: 2.2.26
+
+ The ``max_similarity`` parameter was limited to a minimum value of 0.1.
.. class:: CommonPasswordValidator(password_list_path=DEFAULT_PASSWORD_LIST_PATH)
diff --git a/tests/auth_tests/test_validators.py b/tests/auth_tests/test_validators.py
index 1c2c6b4af..777e51ebd 100644
--- a/tests/auth_tests/test_validators.py
+++ b/tests/auth_tests/test_validators.py
@@ -150,13 +150,10 @@ class UserAttributeSimilarityValidatorTest(TestCase):
max_similarity=1,
).validate(user.first_name, user=user)
self.assertEqual(cm.exception.messages, [expected_error % "first name"])
- # max_similarity=0 rejects all passwords.
- with self.assertRaises(ValidationError) as cm:
- UserAttributeSimilarityValidator(
- user_attributes=['first_name'],
- max_similarity=0,
- ).validate('XXX', user=user)
- self.assertEqual(cm.exception.messages, [expected_error % "first name"])
+ # Very low max_similarity is rejected.
+ msg = 'max_similarity must be at least 0.1'
+ with self.assertRaisesMessage(ValueError, msg):
+ UserAttributeSimilarityValidator(max_similarity=0.09)
# Passes validation.
self.assertIsNone(
UserAttributeSimilarityValidator(user_attributes=['first_name']).validate('testclient', user=user)
diff --git a/tests/file_storage/test_generate_filename.py b/tests/file_storage/test_generate_filename.py
index cb6465092..fd8da6deb 100644
--- a/tests/file_storage/test_generate_filename.py
+++ b/tests/file_storage/test_generate_filename.py
@@ -53,13 +53,20 @@ class GenerateFilenameStorageTests(SimpleTestCase):
s.generate_filename(file_name)
def test_storage_dangerous_paths_dir_name(self):
- file_name = '/tmp/../path'
+ candidates = [
+ ('tmp/../path', 'tmp/..'),
+ ('tmp\\..\\path', 'tmp/..'),
+ ('/tmp/../path', '/tmp/..'),
+ ('\\tmp\\..\\path', '/tmp/..'),
+ ]
s = FileSystemStorage()
- msg = "Detected path traversal attempt in '/tmp/..'"
- with self.assertRaisesMessage(SuspiciousFileOperation, msg):
- s.get_available_name(file_name)
- with self.assertRaisesMessage(SuspiciousFileOperation, msg):
- s.generate_filename(file_name)
+ for file_name, path in candidates:
+ msg = "Detected path traversal attempt in '%s'" % path
+ with self.subTest(file_name=file_name):
+ with self.assertRaisesMessage(SuspiciousFileOperation, msg):
+ s.get_available_name(file_name)
+ with self.assertRaisesMessage(SuspiciousFileOperation, msg):
+ s.generate_filename(file_name)
def test_filefield_dangerous_filename(self):
candidates = [
diff --git a/tests/file_storage/tests.py b/tests/file_storage/tests.py
index 0e692644b..4c6f6920e 100644
--- a/tests/file_storage/tests.py
+++ b/tests/file_storage/tests.py
@@ -291,6 +291,12 @@ class FileStorageTests(SimpleTestCase):
self.storage.delete('path/to/test.file')
+ def test_file_save_abs_path(self):
+ test_name = 'path/to/test.file'
+ f = ContentFile('file saved with path')
+ f_name = self.storage.save(os.path.join(self.temp_dir, test_name), f)
+ self.assertEqual(f_name, test_name)
+
def test_save_doesnt_close(self):
with TemporaryUploadedFile('test', 'text/plain', 1, 'utf8') as file:
file.write(b'1')
diff --git a/tests/template_tests/filter_tests/test_dictsort.py b/tests/template_tests/filter_tests/test_dictsort.py
index 00c2bd42c..3de247fd8 100644
--- a/tests/template_tests/filter_tests/test_dictsort.py
+++ b/tests/template_tests/filter_tests/test_dictsort.py
@@ -1,9 +1,58 @@
-from django.template.defaultfilters import dictsort
+from django.template.defaultfilters import _property_resolver, dictsort
from django.test import SimpleTestCase
+class User:
+ password = 'abc'
+
+ _private = 'private'
+
+ @property
+ def test_property(self):
+ return 'cde'
+
+ def test_method(self):
+ """This is just a test method."""
+
+
class FunctionTests(SimpleTestCase):
+ def test_property_resolver(self):
+ user = User()
+ dict_data = {'a': {
+ 'b1': {'c': 'result1'},
+ 'b2': user,
+ 'b3': {'0': 'result2'},
+ 'b4': [0, 1, 2],
+ }}
+ list_data = ['a', 'b', 'c']
+ tests = [
+ ('a.b1.c', dict_data, 'result1'),
+ ('a.b2.password', dict_data, 'abc'),
+ ('a.b2.test_property', dict_data, 'cde'),
+ # The method should not get called.
+ ('a.b2.test_method', dict_data, user.test_method),
+ ('a.b3.0', dict_data, 'result2'),
+ (0, list_data, 'a'),
+ ]
+ for arg, data, expected_value in tests:
+ with self.subTest(arg=arg):
+ self.assertEqual(_property_resolver(arg)(data), expected_value)
+ # Invalid lookups.
+ fail_tests = [
+ ('a.b1.d', dict_data, AttributeError),
+ ('a.b2.password.0', dict_data, AttributeError),
+ ('a.b2._private', dict_data, AttributeError),
+ ('a.b4.0', dict_data, AttributeError),
+ ('a', list_data, AttributeError),
+ ('0', list_data, TypeError),
+ (4, list_data, IndexError),
+ ]
+ for arg, data, expected_exception in fail_tests:
+ with self.subTest(arg=arg):
+ with self.assertRaises(expected_exception):
+ _property_resolver(arg)(data)
+
def test_sort(self):
sorted_dicts = dictsort(
[{'age': 23, 'name': 'Barbara-Ann'},
@@ -21,7 +70,7 @@ class FunctionTests(SimpleTestCase):
def test_dictsort_complex_sorting_key(self):
"""
- Since dictsort uses template.Variable under the hood, it can sort
+ Since dictsort uses dict.get()/getattr() under the hood, it can sort
on keys like 'foo.bar'.
"""
data = [
@@ -60,3 +109,9 @@ class FunctionTests(SimpleTestCase):
self.assertEqual(dictsort('Hello!', 'age'), '')
self.assertEqual(dictsort({'a': 1}, 'age'), '')
self.assertEqual(dictsort(1, 'age'), '')
+
+ def test_invalid_args(self):
+ """Fail silently if invalid lookups are passed."""
+ self.assertEqual(dictsort([{}], '._private'), '')
+ self.assertEqual(dictsort([{'_private': 'test'}], '_private'), '')
+ self.assertEqual(dictsort([{'nested': {'_private': 'test'}}], 'nested._private'), '')
diff --git a/tests/template_tests/filter_tests/test_dictsortreversed.py b/tests/template_tests/filter_tests/test_dictsortreversed.py
index ada199e12..e2e24e312 100644
--- a/tests/template_tests/filter_tests/test_dictsortreversed.py
+++ b/tests/template_tests/filter_tests/test_dictsortreversed.py
@@ -46,3 +46,9 @@ class FunctionTests(SimpleTestCase):
self.assertEqual(dictsortreversed('Hello!', 'age'), '')
self.assertEqual(dictsortreversed({'a': 1}, 'age'), '')
self.assertEqual(dictsortreversed(1, 'age'), '')
+
+ def test_invalid_args(self):
+ """Fail silently if invalid lookups are passed."""
+ self.assertEqual(dictsortreversed([{}], '._private'), '')
+ self.assertEqual(dictsortreversed([{'_private': 'test'}], '_private'), '')
+ self.assertEqual(dictsortreversed([{'nested': {'_private': 'test'}}], 'nested._private'), '')
Reply to: