[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

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: