diff -Nru python-django-2.2.22/debian/changelog python-django-2.2.23/debian/changelog --- python-django-2.2.22/debian/changelog 2021-05-06 14:52:24.000000000 +0000 +++ python-django-2.2.23/debian/changelog 2021-05-13 09:41:04.000000000 +0000 @@ -1,3 +1,10 @@ +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 -Nru python-django-2.2.22/django/core/files/utils.py python-django-2.2.23/django/core/files/utils.py --- python-django-2.2.22/django/core/files/utils.py 2021-05-06 07:07:46.000000000 +0000 +++ python-django-2.2.23/django/core/files/utils.py 2021-05-13 07:19:37.000000000 +0000 @@ -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 -Nru python-django-2.2.22/django/db/models/fields/files.py python-django-2.2.23/django/db/models/fields/files.py --- python-django-2.2.22/django/db/models/fields/files.py 2021-05-06 07:07:46.000000000 +0000 +++ python-django-2.2.23/django/db/models/fields/files.py 2021-05-13 07:19:37.000000000 +0000 @@ -300,12 +300,12 @@ 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 -Nru python-django-2.2.22/django/__init__.py python-django-2.2.23/django/__init__.py --- python-django-2.2.22/django/__init__.py 2021-05-06 07:08:06.000000000 +0000 +++ python-django-2.2.23/django/__init__.py 2021-05-13 07:19:51.000000000 +0000 @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (2, 2, 22, 'final', 0) +VERSION = (2, 2, 23, 'final', 0) __version__ = get_version(VERSION) diff -Nru python-django-2.2.22/Django.egg-info/PKG-INFO python-django-2.2.23/Django.egg-info/PKG-INFO --- python-django-2.2.22/Django.egg-info/PKG-INFO 2021-05-06 07:09:20.000000000 +0000 +++ python-django-2.2.23/Django.egg-info/PKG-INFO 2021-05-13 07:20:45.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: Django -Version: 2.2.22 +Version: 2.2.23 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 diff -Nru python-django-2.2.22/Django.egg-info/SOURCES.txt python-django-2.2.23/Django.egg-info/SOURCES.txt --- python-django-2.2.22/Django.egg-info/SOURCES.txt 2021-05-06 07:09:21.000000000 +0000 +++ python-django-2.2.23/Django.egg-info/SOURCES.txt 2021-05-13 07:20:46.000000000 +0000 @@ -3832,6 +3832,7 @@ 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.3.txt docs/releases/2.2.4.txt docs/releases/2.2.5.txt diff -Nru python-django-2.2.22/docs/releases/2.2.21.txt python-django-2.2.23/docs/releases/2.2.21.txt --- python-django-2.2.22/docs/releases/2.2.21.txt 2021-05-06 07:07:46.000000000 +0000 +++ python-django-2.2.23/docs/releases/2.2.21.txt 2021-05-13 07:19:37.000000000 +0000 @@ -13,5 +13,4 @@ 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 -Nru python-django-2.2.22/docs/releases/2.2.23.txt python-django-2.2.23/docs/releases/2.2.23.txt --- python-django-2.2.22/docs/releases/2.2.23.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-2.2.23/docs/releases/2.2.23.txt 2021-05-13 07:19:37.000000000 +0000 @@ -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 -Nru python-django-2.2.22/docs/releases/index.txt python-django-2.2.23/docs/releases/index.txt --- python-django-2.2.22/docs/releases/index.txt 2021-05-06 07:07:46.000000000 +0000 +++ python-django-2.2.23/docs/releases/index.txt 2021-05-13 07:19:37.000000000 +0000 @@ -25,6 +25,7 @@ .. toctree:: :maxdepth: 1 + 2.2.23 2.2.22 2.2.21 2.2.20 diff -Nru python-django-2.2.22/docs/releases/security.txt python-django-2.2.23/docs/releases/security.txt --- python-django-2.2.22/docs/releases/security.txt 2021-05-06 07:07:46.000000000 +0000 +++ python-django-2.2.23/docs/releases/security.txt 2021-05-13 07:19:37.000000000 +0000 @@ -1189,3 +1189,17 @@ * 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 -Nru python-django-2.2.22/PKG-INFO python-django-2.2.23/PKG-INFO --- python-django-2.2.22/PKG-INFO 2021-05-06 07:09:21.533331900 +0000 +++ python-django-2.2.23/PKG-INFO 2021-05-13 07:20:46.554731100 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: Django -Version: 2.2.22 +Version: 2.2.23 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 diff -Nru python-django-2.2.22/tests/file_storage/test_generate_filename.py python-django-2.2.23/tests/file_storage/test_generate_filename.py --- python-django-2.2.22/tests/file_storage/test_generate_filename.py 2021-05-06 07:07:46.000000000 +0000 +++ python-django-2.2.23/tests/file_storage/test_generate_filename.py 2021-05-13 07:19:37.000000000 +0000 @@ -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 @@ 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 = f"Could not derive file name from '{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 = f"Detected path traversal attempt in '{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 @@ 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 = f"Detected path traversal attempt in '/tmp/{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 = f"Could not derive file name from '/tmp/{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 -Nru python-django-2.2.22/tests/model_fields/test_filefield.py python-django-2.2.23/tests/model_fields/test_filefield.py --- python-django-2.2.22/tests/model_fields/test_filefield.py 2021-05-06 07:07:43.000000000 +0000 +++ python-django-2.2.23/tests/model_fields/test_filefield.py 2021-05-13 07:19:37.000000000 +0000 @@ -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 @@ 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 = f"Detected path traversal attempt in '{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')