From 50709f081aa46d1e9852fea3503a06284fd05a9c Mon Sep 17 00:00:00 2001 From: starlet-dx <15929766099@163.com> Date: Tue, 16 May 2023 11:27:02 +0800 Subject: [PATCH] Fix CVE-2023-31047 --- CVE-2023-31047.patch | 322 +++++++++++++++++++++++++++++++++++++++++++ python-django.spec | 6 +- 2 files changed, 327 insertions(+), 1 deletion(-) create mode 100644 CVE-2023-31047.patch diff --git a/CVE-2023-31047.patch b/CVE-2023-31047.patch new file mode 100644 index 0000000..33c8681 --- /dev/null +++ b/CVE-2023-31047.patch @@ -0,0 +1,322 @@ +From 7c240c2f1030daf455f04b163fa9807d6bb2a21a Mon Sep 17 00:00:00 2001 +From: starlet-dx <15929766099@163.com> +Date: Tue, 16 May 2023 11:23:26 +0800 +Subject: [PATCH 1/1] [4.1.x] Fixed CVE-2023-31047, Fixed #31710 -- Prevented + potential bypass of validation when uploading multiple files using one form + field. + +Thanks Moataz Al-Sharida and nawaik for reports. + +Co-authored-by: Shai Berger +Co-authored-by: nessita <124304+nessita@users.noreply.github.com> + +Origin: +https://github.com/django/django/commit/e7c3a2ccc3a562328600be05068ed9149e12ce64 +--- + django/forms/widgets.py | 26 ++++++- + docs/topics/http/file-uploads.txt | 64 +++++++++++++++-- + .../forms_tests/field_tests/test_filefield.py | 68 ++++++++++++++++++- + .../widget_tests/test_clearablefileinput.py | 5 ++ + .../widget_tests/test_fileinput.py | 44 ++++++++++++ + 5 files changed, 199 insertions(+), 8 deletions(-) + +diff --git a/django/forms/widgets.py b/django/forms/widgets.py +index 972267b..ce0d4f7 100644 +--- a/django/forms/widgets.py ++++ b/django/forms/widgets.py +@@ -413,17 +413,41 @@ class MultipleHiddenInput(HiddenInput): + + + class FileInput(Input): ++ allow_multiple_selected = False + input_type = "file" + 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 --git a/docs/topics/http/file-uploads.txt b/docs/topics/http/file-uploads.txt +index 94b7432..52a7f09 100644 +--- a/docs/topics/http/file-uploads.txt ++++ b/docs/topics/http/file-uploads.txt +@@ -139,19 +139,53 @@ a :class:`~django.core.files.File` like object to the + instance = ModelWithFileField(file_field=content_file) + instance.save() + ++.. _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 +@@ -171,14 +205,32 @@ uploads: + 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 --git a/tests/forms_tests/field_tests/test_filefield.py b/tests/forms_tests/field_tests/test_filefield.py +index 56aaa31..00c74a7 100644 +--- a/tests/forms_tests/field_tests/test_filefield.py ++++ b/tests/forms_tests/field_tests/test_filefield.py +@@ -2,7 +2,8 @@ import pickle + + 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 + + +@@ -109,3 +110,68 @@ class FileFieldTest(SimpleTestCase): + + 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 --git a/tests/forms_tests/widget_tests/test_clearablefileinput.py b/tests/forms_tests/widget_tests/test_clearablefileinput.py +index fb6a2f6..0b34335 100644 +--- a/tests/forms_tests/widget_tests/test_clearablefileinput.py ++++ b/tests/forms_tests/widget_tests/test_clearablefileinput.py +@@ -232,3 +232,8 @@ class ClearableFileInputTest(WidgetTest): + '', + form.render(), + ) ++ ++ def test_multiple_error(self): ++ msg = "ClearableFileInput doesn't support uploading multiple files." ++ with self.assertRaisesMessage(ValueError, msg): ++ ClearableFileInput(attrs={"multiple": True}) +diff --git a/tests/forms_tests/widget_tests/test_fileinput.py b/tests/forms_tests/widget_tests/test_fileinput.py +index ea73e57..a49f481 100644 +--- a/tests/forms_tests/widget_tests/test_fileinput.py ++++ b/tests/forms_tests/widget_tests/test_fileinput.py +@@ -1,4 +1,6 @@ ++from django.core.files.uploadedfile import SimpleUploadedFile + from django.forms import FileField, FileInput, Form ++from django.utils.datastructures import MultiValueDict + + from .base import WidgetTest + +@@ -48,3 +50,45 @@ class FileInputTest(WidgetTest): + 'name="field" required type="file">', + form.render(), + ) ++ ++ 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) +-- +2.30.0 + diff --git a/python-django.spec b/python-django.spec index 0747542..38836bd 100644 --- a/python-django.spec +++ b/python-django.spec @@ -1,11 +1,12 @@ %global _empty_manifest_terminate_build 0 Name: python-django Version: 4.1.7 -Release: 1 +Release: 2 Summary: A high-level Python Web framework that encourages rapid development and clean, pragmatic design. License: Apache-2.0 and Python-2.0 and BSD-3-Clause URL: https://www.djangoproject.com/ Source0: https://github.com/django/django/archive/%{version}/django-%{version}.tar.gz +Patch0: CVE-2023-31047.patch BuildArch: noarch %description @@ -72,6 +73,9 @@ mv %{buildroot}/doclist.lst . %{_docdir}/* %changelog +* Tue May 16 2023 yaoxin - 4.1.7-2 +- Fix CVE-2023-31047 + * Tue Apr 11 2023 yaoxin - 4.1.7-1 - Update to 4.1.7