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

Bug#1003659: marked as done (bullseye-pu: package python-django/2:2.2.26-1~deb11u1)



Your message dated Sat, 26 Mar 2022 11:59:13 +0000
with message-id <c4d20274f6d76a43fb574d2177f6e3af4235e4be.camel@adam-barratt.org.uk>
and subject line Closing p-u requests for updates in 11.3
has caused the Debian Bug report #1003659,
regarding bullseye-pu: package python-django/2:2.2.26-1~deb11u1
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
1003659: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1003659
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
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'), '')

--- End Message ---
--- Begin Message ---
Package: release.debian.org
Version: 11.3

Hi,

The updates referenced by these bugs were included in stable as part of
this morning's 11.3 point release.

Regards,

Adam

--- End Message ---

Reply to: