diff --git a/Django.egg-info/PKG-INFO b/Django.egg-info/PKG-INFO index 87f2e76e0..bfed6be0f 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.22 +Version: 2.2.24 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 @@ -37,7 +37,7 @@ Description: Django is a high-level Python Web framework that encourages rapid d To get more help: - * Join the ``#django`` channel on irc.freenode.net. Lots of helpful people hang + * 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. @@ -76,5 +76,5 @@ 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 diff --git a/Django.egg-info/SOURCES.txt b/Django.egg-info/SOURCES.txt index 488a23d07..712c72f32 100644 --- a/Django.egg-info/SOURCES.txt +++ b/Django.egg-info/SOURCES.txt @@ -3832,6 +3832,8 @@ docs/releases/2.2.2.txt docs/releases/2.2.20.txt docs/releases/2.2.21.txt docs/releases/2.2.22.txt +docs/releases/2.2.23.txt +docs/releases/2.2.24.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 87f2e76e0..bfed6be0f 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: Django -Version: 2.2.22 +Version: 2.2.24 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 @@ -37,7 +37,7 @@ Description: Django is a high-level Python Web framework that encourages rapid d To get more help: - * Join the ``#django`` channel on irc.freenode.net. Lots of helpful people hang + * 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. @@ -76,5 +76,5 @@ 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 diff --git a/README.rst b/README.rst index 2a33fa2e6..6776b7c39 100644 --- a/README.rst +++ b/README.rst @@ -25,7 +25,7 @@ ticket here: https://code.djangoproject.com/newticket To get more help: -* Join the ``#django`` channel on irc.freenode.net. Lots of helpful people hang +* 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. diff --git a/debian/changelog b/debian/changelog index e665a6d90..6deb982f9 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,47 @@ +python-django (2:2.2.24-1) unstable; urgency=medium + + * New upstream security release. (Closes: #989394) + + - CVE-2021-33203: Potential directory traversal via admindocs + + Staff members could use the admindocs TemplateDetailView view to + check the existence of arbitrary files. Additionally, if (and only + if) the default admindocs templates have been customized by the + developers to also expose the file contents, then not only the + existence but also the file contents would have been exposed. + + As a mitigation, path sanitation is now applied and only files + within the template root directories can be loaded. + + This issue has low severity, according to the Django security + policy. + + Thanks to Rasmus Lerchedahl Petersen and Rasmus Wriedt Larsen from + the CodeQL Python team for the report. + + - CVE-2021-33571: Possible indeterminate SSRF, RFI, and LFI attacks + since validators accepted leading zeros in IPv4 addresses + + URLValidator, validate_ipv4_address(), and + validate_ipv46_address() didn't prohibit leading zeros in octal + literals. If you used such values you could suffer from + indeterminate SSRF, RFI, and LFI attacks. + + validate_ipv4_address() and validate_ipv46_address() validators + were not affected on Python 3.9.5+. + + This issue has medium severity, according to the Django security + policy. + + -- Chris Lamb Wed, 02 Jun 2021 16:15:13 +0100 + +python-django (2:2.2.23-1) unstable; urgency=medium + + * New upstream release. + + + -- Chris Lamb Thu, 13 May 2021 10:41:04 +0100 + python-django (2:2.2.22-1) unstable; urgency=medium * New upstream security release: diff --git a/django/__init__.py b/django/__init__.py index 1683f5273..7963a360d 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (2, 2, 22, 'final', 0) +VERSION = (2, 2, 24, 'final', 0) __version__ = get_version(VERSION) diff --git a/django/contrib/admindocs/views.py b/django/contrib/admindocs/views.py index 0474c38fd..5986717d9 100644 --- a/django/contrib/admindocs/views.py +++ b/django/contrib/admindocs/views.py @@ -15,6 +15,7 @@ from django.db import models from django.http import Http404 from django.template.engine import Engine from django.urls import get_mod_func, get_resolver, get_urlconf +from django.utils._os import safe_join from django.utils.decorators import method_decorator from django.utils.inspect import ( func_accepts_kwargs, func_accepts_var_args, get_func_full_args, @@ -328,7 +329,7 @@ class TemplateDetailView(BaseAdminDocsView): else: # This doesn't account for template loaders (#24128). for index, directory in enumerate(default_engine.dirs): - template_file = Path(directory) / template + template_file = Path(safe_join(directory, template)) if template_file.exists(): with template_file.open() as f: template_contents = f.read() diff --git a/django/core/files/utils.py b/django/core/files/utils.py index f83cb1a3c..f28cea107 100644 --- a/django/core/files/utils.py +++ b/django/core/files/utils.py @@ -1,16 +1,26 @@ import os +import pathlib from django.core.exceptions import SuspiciousFileOperation -def validate_file_name(name): - if name != os.path.basename(name): - raise SuspiciousFileOperation("File name '%s' includes path elements" % name) - +def validate_file_name(name, allow_relative_path=False): # Remove potentially dangerous names - if name in {'', '.', '..'}: + if os.path.basename(name) in {'', '.', '..'}: raise SuspiciousFileOperation("Could not derive file name from '%s'" % name) + if allow_relative_path: + # Use PurePosixPath() because this branch is checked only in + # FileField.generate_filename() where all file paths are expected to be + # Unix style (with forward slashes). + path = pathlib.PurePosixPath(name) + if path.is_absolute() or '..' in path.parts: + raise SuspiciousFileOperation( + "Detected path traversal attempt in '%s'" % name + ) + elif name != os.path.basename(name): + raise SuspiciousFileOperation("File name '%s' includes path elements" % name) + return name diff --git a/django/core/validators.py b/django/core/validators.py index d32b54f68..2da0688e2 100644 --- a/django/core/validators.py +++ b/django/core/validators.py @@ -75,7 +75,7 @@ class URLValidator(RegexValidator): ul = '\u00a1-\uffff' # unicode letters range (must not be a raw string) # IP patterns - ipv4_re = r'(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}' + ipv4_re = r'(?:0|25[0-5]|2[0-4]\d|1\d?\d?|[1-9]\d?)(?:\.(?:0|25[0-5]|2[0-4]\d|1\d?\d?|[1-9]\d?)){3}' ipv6_re = r'\[[0-9a-f:\.]+\]' # (simple regex, validated later) # Host patterns @@ -256,6 +256,18 @@ def validate_ipv4_address(value): ipaddress.IPv4Address(value) except ValueError: raise ValidationError(_('Enter a valid IPv4 address.'), code='invalid') + else: + # Leading zeros are forbidden to avoid ambiguity with the octal + # notation. This restriction is included in Python 3.9.5+. + # TODO: Remove when dropping support for PY39. + if any( + octet != '0' and octet[0] == '0' + for octet in value.split('.') + ): + raise ValidationError( + _('Enter a valid IPv4 address.'), + code='invalid', + ) def validate_ipv6_address(value): diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py index d53bd42be..0f8c3fe48 100644 --- a/django/db/models/fields/files.py +++ b/django/db/models/fields/files.py @@ -300,12 +300,12 @@ class FileField(Field): Until the storage layer, all file paths are expected to be Unix style (with forward slashes). """ - filename = validate_file_name(filename) if callable(self.upload_to): filename = self.upload_to(instance, filename) else: dirname = datetime.datetime.now().strftime(self.upload_to) filename = posixpath.join(dirname, filename) + filename = validate_file_name(filename, allow_relative_path=True) return self.storage.generate_filename(filename) def save_form_data(self, instance, data): diff --git a/docs/faq/help.txt b/docs/faq/help.txt index e2626894e..fe76ba6e1 100644 --- a/docs/faq/help.txt +++ b/docs/faq/help.txt @@ -9,10 +9,9 @@ If this FAQ doesn't contain an answer to your question, you might want to try the |django-users| mailing list. Feel free to ask any question related to installing, using, or debugging Django. -If you prefer IRC, the `#django IRC channel`_ on the Freenode IRC network is an -active community of helpful individuals who may be able to solve your problem. - -.. _`#django IRC channel`: irc://irc.freenode.net/django +If you prefer IRC, the `#django IRC channel`_ on the Libera.Chat IRC network is +an active community of helpful individuals who may be able to solve your +problem. .. _message-does-not-appear-on-django-users: @@ -40,7 +39,7 @@ As with most open-source mailing lists, the folks on |django-users| are volunteers. If nobody has answered your question, it may be because nobody knows the answer, it may be because nobody can understand the question, or it may be that everybody that can help is busy. One thing you might try is to ask -the question on IRC -- visit the `#django IRC channel`_ on the Freenode IRC +the question on IRC -- visit the `#django IRC channel`_ on the Libera.Chat IRC network. You might notice we have a second mailing list, called |django-developers| -- @@ -69,3 +68,4 @@ while a defect is outstanding, we would like to minimize any damage that could be inflicted through public knowledge of that defect. .. _`policy for handling security issues`: ../contributing/#reporting-security-issues +.. _`#django IRC channel`: irc://irc.libera.chat/django diff --git a/docs/index.txt b/docs/index.txt index 6139c3e9b..9c00dc438 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -23,7 +23,7 @@ Having trouble? We'd like to help! .. _archives: https://groups.google.com/group/django-users/ .. _post a question: https://groups.google.com/d/forum/django-users -.. _#django IRC channel: irc://irc.freenode.net/django +.. _#django IRC channel: irc://irc.libera.chat/django .. _ticket tracker: https://code.djangoproject.com/ How the documentation is organized diff --git a/docs/internals/contributing/bugs-and-features.txt b/docs/internals/contributing/bugs-and-features.txt index 858de4ad0..dcaa78ee9 100644 --- a/docs/internals/contributing/bugs-and-features.txt +++ b/docs/internals/contributing/bugs-and-features.txt @@ -166,4 +166,4 @@ Votes on technical matters should be announced and held in public on the .. _searching: https://code.djangoproject.com/search .. _custom queries: https://code.djangoproject.com/query -.. _#django: irc://irc.freenode.net/django +.. _#django: irc://irc.libera.chat/django diff --git a/docs/internals/contributing/index.txt b/docs/internals/contributing/index.txt index 9a1e5d64d..1915c99dd 100644 --- a/docs/internals/contributing/index.txt +++ b/docs/internals/contributing/index.txt @@ -16,7 +16,7 @@ contribute in many ways: friendly and helpful atmosphere. If you're new to the Django community, you should read the `posting guidelines`_. -* Join the `#django IRC channel`_ on Freenode and answer questions. By +* Join the `#django IRC channel`_ on Libera.Chat and answer questions. By explaining Django to other users, you're going to learn a lot about the framework yourself. @@ -68,8 +68,8 @@ Browse the following sections to find out how: committing-code .. _posting guidelines: https://code.djangoproject.com/wiki/UsingTheMailingList -.. _#django IRC channel: irc://irc.freenode.net/django -.. _#django-dev IRC channel: irc://irc.freenode.net/django-dev +.. _#django IRC channel: irc://irc.libera.chat/django +.. _#django-dev IRC channel: irc://irc.libera.chat/django-dev .. _community page: https://www.djangoproject.com/community/ .. _Django forum: https://forum.djangoproject.com/ .. _register it here: https://www.djangoproject.com/community/add/blogs/ diff --git a/docs/intro/contributing.txt b/docs/intro/contributing.txt index eb00190ad..ee889db09 100644 --- a/docs/intro/contributing.txt +++ b/docs/intro/contributing.txt @@ -40,11 +40,11 @@ so that it can be of use to the widest audience. .. admonition:: Where to get help: If you're having trouble going through this tutorial, please post a message - to |django-developers| or drop by `#django-dev on irc.freenode.net`__ to + to |django-developers| or drop by `#django-dev on irc.libera.chat`__ to chat with other Django users who might be able to help. __ https://diveinto.org/python3/table-of-contents.html -__ irc://irc.freenode.net/django-dev +__ irc://irc.libera.chat/django-dev What does this tutorial cover? ------------------------------ diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index 56de527fa..e795e2cf4 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -36,8 +36,8 @@ older versions of Django and install a newer one. .. admonition:: Where to get help: If you're having trouble going through this tutorial, please post a message - to |django-users| or drop by `#django on irc.freenode.net - `_ to chat with other Django users who might + to |django-users| or drop by `#django on irc.libera.chat + `_ to chat with other Django users who might be able to help. Creating a project diff --git a/docs/intro/whatsnext.txt b/docs/intro/whatsnext.txt index 7d3346a12..5b05af4a9 100644 --- a/docs/intro/whatsnext.txt +++ b/docs/intro/whatsnext.txt @@ -127,7 +127,7 @@ particular Django setup, try the |django-users| mailing list or the `#django IRC channel`_ instead. .. _ticket system: https://code.djangoproject.com/ -.. _#django IRC channel: irc://irc.freenode.net/django +.. _#django IRC channel: irc://irc.libera.chat/django In plain text ------------- diff --git a/docs/ref/contrib/gis/install/index.txt b/docs/ref/contrib/gis/install/index.txt index 941afe82d..fa1125abb 100644 --- a/docs/ref/contrib/gis/install/index.txt +++ b/docs/ref/contrib/gis/install/index.txt @@ -109,9 +109,9 @@ Troubleshooting If you can't find the solution to your problem here then participate in the community! You can: -* Join the ``#geodjango`` IRC channel on Freenode. Please be patient and polite - -- while you may not get an immediate response, someone will attempt to answer - your question as soon as they see it. +* Join the ``#django-geo`` IRC channel on Libera.Chat. Please be patient and + polite -- while you may not get an immediate response, someone will attempt + to answer your question as soon as they see it. * Ask your question on the `GeoDjango`__ mailing list. * File a ticket on the `Django trac`__ if you think there's a bug. Make sure to provide a complete description of the problem, versions used, diff --git a/docs/releases/0.95.txt b/docs/releases/0.95.txt index 21fdd1532..4b9b91570 100644 --- a/docs/releases/0.95.txt +++ b/docs/releases/0.95.txt @@ -109,9 +109,9 @@ many common questions appear with some regularity, and any particular problem may already have been answered. Finally, for those who prefer the more immediate feedback offered by IRC, -there's a `#django` channel on irc.freenode.net that is regularly populated -by Django users and developers from around the world. Friendly people are -usually available at any hour of the day -- to help, or just to chat. +there's a ``#django`` channel on ``irc.libera.chat`` that is regularly +populated by Django users and developers from around the world. Friendly people +are usually available at any hour of the day -- to help, or just to chat. .. _Django website: https://www.djangoproject.com/ .. _django-users: https://groups.google.com/group/django-users diff --git a/docs/releases/1.1.txt b/docs/releases/1.1.txt index 49c375b5c..e55ef9c90 100644 --- a/docs/releases/1.1.txt +++ b/docs/releases/1.1.txt @@ -441,7 +441,7 @@ What's next? We'll take a short break, and then work on Django 1.2 will begin -- no rest for the weary! If you'd like to help, discussion of Django development, including progress toward the 1.2 release, takes place daily on the |django-developers| -mailing list and in the ``#django-dev`` IRC channel on ``irc.freenode.net``. +mailing list and in the ``#django-dev`` IRC channel on ``irc.libera.chat``. Feel free to join the discussions! Django's online documentation also includes pointers on how to contribute to diff --git a/docs/releases/2.2.21.txt b/docs/releases/2.2.21.txt index f32aeadff..2302df428 100644 --- a/docs/releases/2.2.21.txt +++ b/docs/releases/2.2.21.txt @@ -13,5 +13,4 @@ CVE-2021-31542: Potential directory-traversal via uploaded files directory-traversal via uploaded files with suitably crafted file names. In order to mitigate this risk, stricter basename and path sanitation is now -applied. Specifically, empty file names and paths with dot segments will be -rejected. +applied. diff --git a/docs/releases/2.2.23.txt b/docs/releases/2.2.23.txt new file mode 100644 index 000000000..6c39361e5 --- /dev/null +++ b/docs/releases/2.2.23.txt @@ -0,0 +1,15 @@ +=========================== +Django 2.2.23 release notes +=========================== + +*May 13, 2021* + +Django 2.2.23 fixes a regression in 2.2.21. + +Bugfixes +======== + +* Fixed a regression in Django 2.2.21 where saving ``FileField`` would raise a + ``SuspiciousFileOperation`` even when a custom + :attr:`~django.db.models.FileField.upload_to` returns a valid file path + (:ticket:`32718`). diff --git a/docs/releases/2.2.24.txt b/docs/releases/2.2.24.txt new file mode 100644 index 000000000..1064fc53a --- /dev/null +++ b/docs/releases/2.2.24.txt @@ -0,0 +1,32 @@ +=========================== +Django 2.2.24 release notes +=========================== + +*June 2, 2021* + +Django 2.2.24 fixes two security issues in 2.2.23. + +CVE-2021-33203: Potential directory traversal via ``admindocs`` +=============================================================== + +Staff members could use the :mod:`~django.contrib.admindocs` +``TemplateDetailView`` view to check the existence of arbitrary files. +Additionally, if (and only if) the default admindocs templates have been +customized by the developers to also expose the file contents, then not only +the existence but also the file contents would have been exposed. + +As a mitigation, path sanitation is now applied and only files within the +template root directories can be loaded. + +CVE-2021-33571: Possible indeterminate SSRF, RFI, and LFI attacks since validators accepted leading zeros in IPv4 addresses +=========================================================================================================================== + +:class:`~django.core.validators.URLValidator`, +:func:`~django.core.validators.validate_ipv4_address`, and +:func:`~django.core.validators.validate_ipv46_address` didn't prohibit leading +zeros in octal literals. If you used such values you could suffer from +indeterminate SSRF, RFI, and LFI attacks. + +:func:`~django.core.validators.validate_ipv4_address` and +:func:`~django.core.validators.validate_ipv46_address` validators were not +affected on Python 3.9.5+. diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 4262a97ac..38bb561b9 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -25,6 +25,8 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 2.2.24 + 2.2.23 2.2.22 2.2.21 2.2.20 diff --git a/docs/releases/security.txt b/docs/releases/security.txt index 3c231730e..509cc6ce7 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -1189,3 +1189,17 @@ Versions affected * Django 3.2 :commit:`(patch) ` * Django 3.1 :commit:`(patch) <25d84d64122c15050a0ee739e859f22ddab5ac48>` * Django 2.2 :commit:`(patch) <04ac1624bdc2fa737188401757cf95ced122d26d>` + +May 6, 2021 - :cve:`2021-32052` +------------------------------- + +Header injection possibility since ``URLValidator`` accepted newlines in input +on Python 3.9.5+. `Full description +`__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 3.2 :commit:`(patch) <2d2c1d0c97832860fbd6597977e2aae17dd7e5b2>` +* Django 3.1 :commit:`(patch) ` +* Django 2.2 :commit:`(patch) ` diff --git a/docs/spelling_wordlist b/docs/spelling_wordlist index f8a718a3b..c511bdb3c 100644 --- a/docs/spelling_wordlist +++ b/docs/spelling_wordlist @@ -222,7 +222,6 @@ formfield formset formsets formtools -freenode Frysian functionalities gdal @@ -320,6 +319,7 @@ Kyngesburye latin lawrence lexer +Libera lifecycle lifecycles linearize diff --git a/docs/topics/db/sql.txt b/docs/topics/db/sql.txt index 84420c7e4..13d5a83c2 100644 --- a/docs/topics/db/sql.txt +++ b/docs/topics/db/sql.txt @@ -23,8 +23,8 @@ __ `executing custom SQL directly`_ :doc:`custom query expressions `. Before using raw SQL, explore :doc:`the ORM `. Ask on - |django-users| or the `#django IRC channel - `_ to see if the ORM supports your use case. + one of :doc:`the support channels ` to see if the ORM supports + your use case. .. warning:: diff --git a/tests/admin_docs/test_views.py b/tests/admin_docs/test_views.py index bcadff7d8..dc6d3c127 100644 --- a/tests/admin_docs/test_views.py +++ b/tests/admin_docs/test_views.py @@ -134,6 +134,22 @@ class AdminDocViewTests(TestDataMixin, AdminDocsTestCase): self.assertContains(response, 'View documentation') +@unittest.skipUnless(utils.docutils_is_available, 'no docutils installed.') +class AdminDocViewDefaultEngineOnly(TestDataMixin, AdminDocsTestCase): + + def setUp(self): + self.client.force_login(self.superuser) + + def test_template_detail_path_traversal(self): + cases = ['/etc/passwd', '../passwd'] + for fpath in cases: + with self.subTest(path=fpath): + response = self.client.get( + reverse('django-admindocs-templates', args=[fpath]), + ) + self.assertEqual(response.status_code, 400) + + @override_settings(TEMPLATES=[{ 'NAME': 'ONE', 'BACKEND': 'django.template.backends.django.DjangoTemplates', diff --git a/tests/file_storage/test_generate_filename.py b/tests/file_storage/test_generate_filename.py index 4746a53f6..cb6465092 100644 --- a/tests/file_storage/test_generate_filename.py +++ b/tests/file_storage/test_generate_filename.py @@ -1,6 +1,4 @@ import os -import sys -from unittest import skipIf from django.core.exceptions import SuspiciousFileOperation from django.core.files.base import ContentFile @@ -64,19 +62,37 @@ class GenerateFilenameStorageTests(SimpleTestCase): s.generate_filename(file_name) def test_filefield_dangerous_filename(self): - candidates = ['..', '.', '', '???', '$.$.$'] + candidates = [ + ('..', 'some/folder/..'), + ('.', 'some/folder/.'), + ('', 'some/folder/'), + ('???', '???'), + ('$.$.$', '$.$.$'), + ] f = FileField(upload_to='some/folder/') - msg = "Could not derive file name from '%s'" - for file_name in candidates: + for file_name, msg_file_name in candidates: + msg = "Could not derive file name from '%s'" % msg_file_name with self.subTest(file_name=file_name): - with self.assertRaisesMessage(SuspiciousFileOperation, msg % file_name): + with self.assertRaisesMessage(SuspiciousFileOperation, msg): f.generate_filename(None, file_name) - def test_filefield_dangerous_filename_dir(self): + def test_filefield_dangerous_filename_dot_segments(self): f = FileField(upload_to='some/folder/') - msg = "File name '/tmp/path' includes path elements" + msg = "Detected path traversal attempt in 'some/folder/../path'" with self.assertRaisesMessage(SuspiciousFileOperation, msg): - f.generate_filename(None, '/tmp/path') + f.generate_filename(None, '../path') + + def test_filefield_generate_filename_absolute_path(self): + f = FileField(upload_to='some/folder/') + candidates = [ + '/tmp/path', + '/tmp/../path', + ] + for file_name in candidates: + msg = "Detected path traversal attempt in '%s'" % file_name + with self.subTest(file_name=file_name): + with self.assertRaisesMessage(SuspiciousFileOperation, msg): + f.generate_filename(None, file_name) def test_filefield_generate_filename(self): f = FileField(upload_to='some/folder/') @@ -95,7 +111,57 @@ class GenerateFilenameStorageTests(SimpleTestCase): os.path.normpath('some/folder/test_with_space.txt') ) - @skipIf(sys.platform == 'win32', 'Path components in filename are not supported after 0b79eb3.') + def test_filefield_generate_filename_upload_to_overrides_dangerous_filename(self): + def upload_to(instance, filename): + return 'test.txt' + + f = FileField(upload_to=upload_to) + candidates = [ + '/tmp/.', + '/tmp/..', + '/tmp/../path', + '/tmp/path', + 'some/folder/', + 'some/folder/.', + 'some/folder/..', + 'some/folder/???', + 'some/folder/$.$.$', + 'some/../test.txt', + '', + ] + for file_name in candidates: + with self.subTest(file_name=file_name): + self.assertEqual(f.generate_filename(None, file_name), 'test.txt') + + def test_filefield_generate_filename_upload_to_absolute_path(self): + def upload_to(instance, filename): + return '/tmp/' + filename + + f = FileField(upload_to=upload_to) + candidates = [ + 'path', + '../path', + '???', + '$.$.$', + ] + for file_name in candidates: + msg = "Detected path traversal attempt in '/tmp/%s'" % file_name + with self.subTest(file_name=file_name): + with self.assertRaisesMessage(SuspiciousFileOperation, msg): + f.generate_filename(None, file_name) + + def test_filefield_generate_filename_upload_to_dangerous_filename(self): + def upload_to(instance, filename): + return '/tmp/' + filename + + f = FileField(upload_to=upload_to) + candidates = ['..', '.', ''] + for file_name in candidates: + msg = "Could not derive file name from '/tmp/%s'" % file_name + with self.subTest(file_name=file_name): + with self.assertRaisesMessage(SuspiciousFileOperation, msg): + f.generate_filename(None, file_name) + def test_filefield_awss3_storage(self): """ Simulate a FileField with an S3 storage which uses keys rather than diff --git a/tests/model_fields/test_filefield.py b/tests/model_fields/test_filefield.py index 9330a2eba..0afef7284 100644 --- a/tests/model_fields/test_filefield.py +++ b/tests/model_fields/test_filefield.py @@ -1,8 +1,10 @@ import os import sys +import tempfile import unittest -from django.core.files import temp +from django.core.exceptions import SuspiciousFileOperation +from django.core.files import File, temp from django.core.files.base import ContentFile from django.core.files.uploadedfile import TemporaryUploadedFile from django.db.utils import IntegrityError @@ -59,6 +61,15 @@ class FileFieldTests(TestCase): d.refresh_from_db() self.assertIs(d.myfile.instance, d) + @unittest.skipIf(sys.platform == 'win32', "Crashes with OSError on Windows.") + def test_save_without_name(self): + with tempfile.NamedTemporaryFile(suffix='.txt') as tmp: + document = Document.objects.create(myfile='something.txt') + document.myfile = File(tmp) + msg = "Detected path traversal attempt in '%s'" % tmp.name + with self.assertRaisesMessage(SuspiciousFileOperation, msg): + document.save() + def test_defer(self): Document.objects.create(myfile='something.txt') self.assertEqual(Document.objects.defer('myfile')[0].myfile, 'something.txt') diff --git a/tests/validators/invalid_urls.txt b/tests/validators/invalid_urls.txt index 4a092034f..a5a41ba84 100644 --- a/tests/validators/invalid_urls.txt +++ b/tests/validators/invalid_urls.txt @@ -46,6 +46,14 @@ http://1.1.1.1.1 http://123.123.123 http://3628126748 http://123 +http://000.000.000.000 +http://016.016.016.016 +http://192.168.000.001 +http://01.2.3.4 +http://01.2.3.4 +http://1.02.3.4 +http://1.2.03.4 +http://1.2.3.04 http://.www.foo.bar/ http://.www.foo.bar./ http://[::1:2::3]:8080/ diff --git a/tests/validators/tests.py b/tests/validators/tests.py index 012b098f4..1f09fb53f 100644 --- a/tests/validators/tests.py +++ b/tests/validators/tests.py @@ -135,6 +135,16 @@ TEST_DATA = [ (validate_ipv4_address, '1.1.1.1\n', ValidationError), (validate_ipv4_address, '٧.2٥.3٣.243', ValidationError), + # Leading zeros are forbidden to avoid ambiguity with the octal notation. + (validate_ipv4_address, '000.000.000.000', ValidationError), + (validate_ipv4_address, '016.016.016.016', ValidationError), + (validate_ipv4_address, '192.168.000.001', ValidationError), + (validate_ipv4_address, '01.2.3.4', ValidationError), + (validate_ipv4_address, '01.2.3.4', ValidationError), + (validate_ipv4_address, '1.02.3.4', ValidationError), + (validate_ipv4_address, '1.2.03.4', ValidationError), + (validate_ipv4_address, '1.2.3.04', ValidationError), + # validate_ipv6_address uses django.utils.ipv6, which # is tested in much greater detail in its own testcase (validate_ipv6_address, 'fe80::1', None), @@ -160,6 +170,16 @@ TEST_DATA = [ (validate_ipv46_address, '::zzz', ValidationError), (validate_ipv46_address, '12345::', ValidationError), + # Leading zeros are forbidden to avoid ambiguity with the octal notation. + (validate_ipv46_address, '000.000.000.000', ValidationError), + (validate_ipv46_address, '016.016.016.016', ValidationError), + (validate_ipv46_address, '192.168.000.001', ValidationError), + (validate_ipv46_address, '01.2.3.4', ValidationError), + (validate_ipv46_address, '01.2.3.4', ValidationError), + (validate_ipv46_address, '1.02.3.4', ValidationError), + (validate_ipv46_address, '1.2.03.4', ValidationError), + (validate_ipv46_address, '1.2.3.04', ValidationError), + (validate_comma_separated_integer_list, '1', None), (validate_comma_separated_integer_list, '12', None), (validate_comma_separated_integer_list, '1,2', None), diff --git a/tests/validators/valid_urls.txt b/tests/validators/valid_urls.txt index f79f94814..ef9e563f8 100644 --- a/tests/validators/valid_urls.txt +++ b/tests/validators/valid_urls.txt @@ -63,6 +63,12 @@ http://0.0.0.0/ http://255.255.255.255 http://224.0.0.0 http://224.1.1.1 +http://111.112.113.114/ +http://88.88.88.88/ +http://11.12.13.14/ +http://10.20.30.40/ +http://1.2.3.4/ +http://127.0.01.09.home.lan http://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.example.com http://example.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com http://example.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa