Package: release.debian.org Severity: normal Tags: bullseye User: release.debian....@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 `. `'` la...@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 <la...@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 <cla...@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'), '')