diffstat for python-django-3.2.18 python-django-3.2.19 Django.egg-info/PKG-INFO | 2 Django.egg-info/SOURCES.txt | 1 PKG-INFO | 2 debian/changelog | 25 +++++ debian/control | 2 django/__init__.py | 2 django/forms/widgets.py | 26 +++++ docs/releases/1.7.txt | 4 docs/releases/3.2.19.txt | 23 ++++ docs/releases/index.txt | 1 docs/releases/security.txt | 10 ++ docs/topics/http/file-uploads.txt | 65 ++++++++++++- tests/forms_tests/field_tests/test_filefield.py | 68 +++++++++++++- tests/forms_tests/widget_tests/test_clearablefileinput.py | 5 + tests/forms_tests/widget_tests/test_fileinput.py | 44 +++++++++ 15 files changed, 266 insertions(+), 14 deletions(-) diff -Nru python-django-3.2.18/Django.egg-info/PKG-INFO python-django-3.2.19/Django.egg-info/PKG-INFO --- python-django-3.2.18/Django.egg-info/PKG-INFO 2023-02-14 00:05:31.000000000 -0800 +++ python-django-3.2.19/Django.egg-info/PKG-INFO 2023-05-03 04:59:48.000000000 -0700 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: Django -Version: 3.2.18 +Version: 3.2.19 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-3.2.18/Django.egg-info/SOURCES.txt python-django-3.2.19/Django.egg-info/SOURCES.txt --- python-django-3.2.18/Django.egg-info/SOURCES.txt 2023-02-14 00:05:31.000000000 -0800 +++ python-django-3.2.19/Django.egg-info/SOURCES.txt 2023-05-03 04:59:48.000000000 -0700 @@ -4037,6 +4037,7 @@ docs/releases/3.2.16.txt docs/releases/3.2.17.txt docs/releases/3.2.18.txt +docs/releases/3.2.19.txt docs/releases/3.2.2.txt docs/releases/3.2.3.txt docs/releases/3.2.4.txt diff -Nru python-django-3.2.18/PKG-INFO python-django-3.2.19/PKG-INFO --- python-django-3.2.18/PKG-INFO 2023-02-14 00:05:32.867672000 -0800 +++ python-django-3.2.19/PKG-INFO 2023-05-03 04:59:49.717188000 -0700 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: Django -Version: 3.2.18 +Version: 3.2.19 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-3.2.18/debian/changelog python-django-3.2.19/debian/changelog --- python-django-3.2.18/debian/changelog 2023-02-14 09:12:57.000000000 -0800 +++ python-django-3.2.19/debian/changelog 2023-05-03 09:32:59.000000000 -0700 @@ -1,3 +1,28 @@ +python-django (3:3.2.19-1) unstable; urgency=medium + + * New upstream security release. + * CVE-2023-31047: Prevent a potential bypass of validation when uploading + multiple files using one form field. + + Uploading multiple files using one form field has never been supported by + forms.FileField or forms.ImageField as only the last uploaded file was + validated. Unfortunately, Uploading multiple files topic suggested + otherwise. In order to avoid the vulnerability, the ClearableFileInput and + FileInput form widgets now raise ValueError when the multiple HTML + attribute is set on them. To prevent the exception and keep the old + behavior, set the allow_multiple_selected attribute to True. + + For more details on using the new attribute and handling of multiple files + through a single field, see: + + + + (Closes: #1035467) + + * Bump Standards-Version to 4.6.2. + + -- Chris Lamb Wed, 03 May 2023 09:32:59 -0700 + python-django (3:3.2.18-1) unstable; urgency=high * New upstream security release: diff -Nru python-django-3.2.18/debian/control python-django-3.2.19/debian/control --- python-django-3.2.18/debian/control 2023-02-14 09:12:57.000000000 -0800 +++ python-django-3.2.19/debian/control 2023-05-03 09:32:59.000000000 -0700 @@ -7,7 +7,7 @@ Raphaël Hertzog , Brian May , Chris Lamb , -Standards-Version: 4.6.1 +Standards-Version: 4.6.2 Build-Depends: debhelper-compat (= 13), dh-python, diff -Nru python-django-3.2.18/django/__init__.py python-django-3.2.19/django/__init__.py --- python-django-3.2.18/django/__init__.py 2023-02-14 00:03:45.000000000 -0800 +++ python-django-3.2.19/django/__init__.py 2023-05-03 04:59:16.000000000 -0700 @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (3, 2, 18, 'final', 0) +VERSION = (3, 2, 19, 'final', 0) __version__ = get_version(VERSION) diff -Nru python-django-3.2.18/django/forms/widgets.py python-django-3.2.19/django/forms/widgets.py --- python-django-3.2.18/django/forms/widgets.py 2023-02-13 23:52:45.000000000 -0800 +++ python-django-3.2.19/django/forms/widgets.py 2023-05-03 04:58:44.000000000 -0700 @@ -378,16 +378,40 @@ class FileInput(Input): input_type = 'file' + allow_multiple_selected = False needs_multipart_form = True template_name = 'django/forms/widgets/file.html' + def __init__(self, attrs=None): + if ( + attrs is not None and + not self.allow_multiple_selected and + attrs.get("multiple", False) + ): + raise ValueError( + "%s doesn't support uploading multiple files." + % self.__class__.__qualname__ + ) + if self.allow_multiple_selected: + if attrs is None: + attrs = {"multiple": True} + else: + attrs.setdefault("multiple", True) + super().__init__(attrs) + def format_value(self, value): """File input never renders a value.""" return def value_from_datadict(self, data, files, name): "File widgets take data from FILES, not POST" - return files.get(name) + getter = files.get + if self.allow_multiple_selected: + try: + getter = files.getlist + except AttributeError: + pass + return getter(name) def value_omitted_from_data(self, data, files, name): return name not in files diff -Nru python-django-3.2.18/docs/releases/1.7.txt python-django-3.2.19/docs/releases/1.7.txt --- python-django-3.2.18/docs/releases/1.7.txt 2023-02-13 23:52:45.000000000 -0800 +++ python-django-3.2.19/docs/releases/1.7.txt 2023-05-03 04:58:23.000000000 -0700 @@ -1196,8 +1196,8 @@ the way routing information cascaded over joins. See :ticket:`13724` for more details. -pytz may be required --------------------- +``pytz`` may be required +------------------------ If your project handles datetimes before 1970 or after 2037 and Django raises a :exc:`ValueError` when encountering them, you will have to install pytz_. You diff -Nru python-django-3.2.18/docs/releases/3.2.19.txt python-django-3.2.19/docs/releases/3.2.19.txt --- python-django-3.2.18/docs/releases/3.2.19.txt 1969-12-31 16:00:00.000000000 -0800 +++ python-django-3.2.19/docs/releases/3.2.19.txt 2023-05-03 04:58:44.000000000 -0700 @@ -0,0 +1,23 @@ +=========================== +Django 3.2.19 release notes +=========================== + +*May 3, 2023* + +Django 3.2.19 fixes a security issue with severity "low" in 3.2.18. + +CVE-2023-31047: Potential bypass of validation when uploading multiple files using one form field +================================================================================================= + +Uploading multiple files using one form field has never been supported by +:class:`.forms.FileField` or :class:`.forms.ImageField` as only the last +uploaded file was validated. Unfortunately, :ref:`uploading_multiple_files` +topic suggested otherwise. + +In order to avoid the vulnerability, :class:`~django.forms.ClearableFileInput` +and :class:`~django.forms.FileInput` form widgets now raise ``ValueError`` when +the ``multiple`` HTML attribute is set on them. To prevent the exception and +keep the old behavior, set ``allow_multiple_selected`` to ``True``. + +For more details on using the new attribute and handling of multiple files +through a single field, see :ref:`uploading_multiple_files`. diff -Nru python-django-3.2.18/docs/releases/index.txt python-django-3.2.19/docs/releases/index.txt --- python-django-3.2.18/docs/releases/index.txt 2023-02-13 23:52:45.000000000 -0800 +++ python-django-3.2.19/docs/releases/index.txt 2023-05-03 04:58:23.000000000 -0700 @@ -25,6 +25,7 @@ .. toctree:: :maxdepth: 1 + 3.2.19 3.2.18 3.2.17 3.2.16 diff -Nru python-django-3.2.18/docs/releases/security.txt python-django-3.2.19/docs/releases/security.txt --- python-django-3.2.18/docs/releases/security.txt 2023-02-08 06:09:30.000000000 -0800 +++ python-django-3.2.19/docs/releases/security.txt 2023-05-03 04:58:23.000000000 -0700 @@ -36,6 +36,16 @@ All security issues have been handled under versions of Django's security process. These are listed below. +February 14, 2023 - :cve:`2023-24580` +------------------------------------- + +Potential denial-of-service vulnerability in file uploads. `Full description +`__ + +* Django 4.1 :commit:`(patch) <628b33a854a9c68ec8a0c51f382f304a0044ec92>` +* Django 4.0 :commit:`(patch) <83f1ea83e4553e211c1c5a0dfc197b66d4e50432>` +* Django 3.2 :commit:`(patch) ` + February 1, 2023 - :cve:`2023-23969` ------------------------------------ diff -Nru python-django-3.2.18/docs/topics/http/file-uploads.txt python-django-3.2.19/docs/topics/http/file-uploads.txt --- python-django-3.2.18/docs/topics/http/file-uploads.txt 2023-02-13 23:52:45.000000000 -0800 +++ python-django-3.2.19/docs/topics/http/file-uploads.txt 2023-05-03 04:58:44.000000000 -0700 @@ -126,19 +126,54 @@ form = UploadFileForm() return render(request, 'upload.html', {'form': form}) +.. _uploading_multiple_files: + Uploading multiple files ------------------------ -If you want to upload multiple files using one form field, set the ``multiple`` -HTML attribute of field's widget: +.. + Tests in tests.forms_tests.field_tests.test_filefield.MultipleFileFieldTest + should be updated after any changes in the following snippets. + +If you want to upload multiple files using one form field, create a subclass +of the field's widget and set the ``allow_multiple_selected`` attribute on it +to ``True``. + +In order for such files to be all validated by your form (and have the value of +the field include them all), you will also have to subclass ``FileField``. See +below for an example. + +.. admonition:: Multiple file field + + Django is likely to have a proper multiple file field support at some point + in the future. .. code-block:: python :caption: ``forms.py`` from django import forms + + class MultipleFileInput(forms.ClearableFileInput): + allow_multiple_selected = True + + + class MultipleFileField(forms.FileField): + def __init__(self, *args, **kwargs): + kwargs.setdefault("widget", MultipleFileInput()) + super().__init__(*args, **kwargs) + + def clean(self, data, initial=None): + single_file_clean = super().clean + if isinstance(data, (list, tuple)): + result = [single_file_clean(d, initial) for d in data] + else: + result = single_file_clean(data, initial) + return result + + class FileFieldForm(forms.Form): - file_field = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True})) + file_field = MultipleFileField() Then override the ``post`` method of your :class:`~django.views.generic.edit.FormView` subclass to handle multiple file @@ -158,14 +193,32 @@ def post(self, request, *args, **kwargs): form_class = self.get_form_class() form = self.get_form(form_class) - files = request.FILES.getlist('file_field') if form.is_valid(): - for f in files: - ... # Do something with each file. return self.form_valid(form) else: return self.form_invalid(form) + def form_valid(self, form): + files = form.cleaned_data["file_field"] + for f in files: + ... # Do something with each file. + return super().form_valid() + +.. warning:: + + This will allow you to handle multiple files at the form level only. Be + aware that you cannot use it to put multiple files on a single model + instance (in a single field), for example, even if the custom widget is used + with a form field related to a model ``FileField``. + +.. versionchanged:: 3.2.19 + + In previous versions, there was no support for the ``allow_multiple_selected`` + class attribute, and users were advised to create the widget with the HTML + attribute ``multiple`` set through the ``attrs`` argument. However, this + caused validation of the form field to be applied only to the last file + submitted, which could have adverse security implications. + Upload Handlers =============== diff -Nru python-django-3.2.18/tests/forms_tests/field_tests/test_filefield.py python-django-3.2.19/tests/forms_tests/field_tests/test_filefield.py --- python-django-3.2.18/tests/forms_tests/field_tests/test_filefield.py 2023-02-13 23:52:45.000000000 -0800 +++ python-django-3.2.19/tests/forms_tests/field_tests/test_filefield.py 2023-05-03 04:58:44.000000000 -0700 @@ -2,7 +2,8 @@ from django.core.exceptions import ValidationError from django.core.files.uploadedfile import SimpleUploadedFile -from django.forms import FileField +from django.core.validators import validate_image_file_extension +from django.forms import FileField, FileInput from django.test import SimpleTestCase @@ -83,3 +84,68 @@ def test_file_picklable(self): self.assertIsInstance(pickle.loads(pickle.dumps(FileField())), FileField) + + +class MultipleFileInput(FileInput): + allow_multiple_selected = True + + +class MultipleFileField(FileField): + def __init__(self, *args, **kwargs): + kwargs.setdefault("widget", MultipleFileInput()) + super().__init__(*args, **kwargs) + + def clean(self, data, initial=None): + single_file_clean = super().clean + if isinstance(data, (list, tuple)): + result = [single_file_clean(d, initial) for d in data] + else: + result = single_file_clean(data, initial) + return result + + +class MultipleFileFieldTest(SimpleTestCase): + def test_file_multiple(self): + f = MultipleFileField() + files = [ + SimpleUploadedFile("name1", b"Content 1"), + SimpleUploadedFile("name2", b"Content 2"), + ] + self.assertEqual(f.clean(files), files) + + def test_file_multiple_empty(self): + f = MultipleFileField() + files = [ + SimpleUploadedFile("empty", b""), + SimpleUploadedFile("nonempty", b"Some Content"), + ] + msg = "'The submitted file is empty.'" + with self.assertRaisesMessage(ValidationError, msg): + f.clean(files) + with self.assertRaisesMessage(ValidationError, msg): + f.clean(files[::-1]) + + def test_file_multiple_validation(self): + f = MultipleFileField(validators=[validate_image_file_extension]) + + good_files = [ + SimpleUploadedFile("image1.jpg", b"fake JPEG"), + SimpleUploadedFile("image2.png", b"faux image"), + SimpleUploadedFile("image3.bmp", b"fraudulent bitmap"), + ] + self.assertEqual(f.clean(good_files), good_files) + + evil_files = [ + SimpleUploadedFile("image1.sh", b"#!/bin/bash -c 'echo pwned!'\n"), + SimpleUploadedFile("image2.png", b"faux image"), + SimpleUploadedFile("image3.jpg", b"fake JPEG"), + ] + + evil_rotations = ( + evil_files[i:] + evil_files[:i] # Rotate by i. + for i in range(len(evil_files)) + ) + msg = "File extension “sh” is not allowed. Allowed extensions are: " + for rotated_evil_files in evil_rotations: + with self.assertRaisesMessage(ValidationError, msg): + f.clean(rotated_evil_files) diff -Nru python-django-3.2.18/tests/forms_tests/widget_tests/test_clearablefileinput.py python-django-3.2.19/tests/forms_tests/widget_tests/test_clearablefileinput.py --- python-django-3.2.18/tests/forms_tests/widget_tests/test_clearablefileinput.py 2023-02-13 23:52:45.000000000 -0800 +++ python-django-3.2.19/tests/forms_tests/widget_tests/test_clearablefileinput.py 2023-05-03 04:58:44.000000000 -0700 @@ -176,3 +176,8 @@ self.assertIs(widget.value_omitted_from_data({}, {}, 'field'), True) self.assertIs(widget.value_omitted_from_data({}, {'field': 'x'}, 'field'), False) self.assertIs(widget.value_omitted_from_data({'field-clear': 'y'}, {}, 'field'), False) + + def test_multiple_error(self): + msg = "ClearableFileInput doesn't support uploading multiple files." + with self.assertRaisesMessage(ValueError, msg): + ClearableFileInput(attrs={"multiple": True}) diff -Nru python-django-3.2.18/tests/forms_tests/widget_tests/test_fileinput.py python-django-3.2.19/tests/forms_tests/widget_tests/test_fileinput.py --- python-django-3.2.18/tests/forms_tests/widget_tests/test_fileinput.py 2023-02-13 23:52:45.000000000 -0800 +++ python-django-3.2.19/tests/forms_tests/widget_tests/test_fileinput.py 2023-05-03 04:58:44.000000000 -0700 @@ -1,4 +1,6 @@ +from django.core.files.uploadedfile import SimpleUploadedFile from django.forms import FileInput +from django.utils.datastructures import MultiValueDict from .base import WidgetTest @@ -24,3 +26,45 @@ # user to keep the existing, initial value. self.assertIs(self.widget.use_required_attribute(None), True) self.assertIs(self.widget.use_required_attribute('resume.txt'), False) + + def test_multiple_error(self): + msg = "FileInput doesn't support uploading multiple files." + with self.assertRaisesMessage(ValueError, msg): + FileInput(attrs={"multiple": True}) + + def test_value_from_datadict_multiple(self): + class MultipleFileInput(FileInput): + allow_multiple_selected = True + + file_1 = SimpleUploadedFile("something1.txt", b"content 1") + file_2 = SimpleUploadedFile("something2.txt", b"content 2") + # Uploading multiple files is allowed. + widget = MultipleFileInput(attrs={"multiple": True}) + value = widget.value_from_datadict( + data={"name": "Test name"}, + files=MultiValueDict({"myfile": [file_1, file_2]}), + name="myfile", + ) + self.assertEqual(value, [file_1, file_2]) + # Uploading multiple files is not allowed. + widget = FileInput() + value = widget.value_from_datadict( + data={"name": "Test name"}, + files=MultiValueDict({"myfile": [file_1, file_2]}), + name="myfile", + ) + self.assertEqual(value, file_2) + + def test_multiple_default(self): + class MultipleFileInput(FileInput): + allow_multiple_selected = True + + tests = [ + (None, True), + ({"class": "myclass"}, True), + ({"multiple": False}, False), + ] + for attrs, expected in tests: + with self.subTest(attrs=attrs): + widget = MultipleFileInput(attrs=attrs) + self.assertIs(widget.attrs["multiple"], expected)