summaryrefslogtreecommitdiffstats
path: root/meta-python/recipes-devtools/python/python3-django
diff options
context:
space:
mode:
authorNarpat Mali <narpat.mali@windriver.com>2023-05-26 13:41:37 +0000
committerArmin Kuster <akuster808@gmail.com>2023-06-17 13:49:44 -0400
commite43d06878866f55b1f261fd06e0b443d378e3ce8 (patch)
treec12fd8e05c360677fc3b6216c96f632f2779f41d /meta-python/recipes-devtools/python/python3-django
parent26b9ab59fcad04975377a403dafe05aa8acf3a0e (diff)
downloadmeta-openembedded-e43d06878866f55b1f261fd06e0b443d378e3ce8.tar.gz
python3-django: fix for CVE-2023-31047
In Django 3.2 before 3.2.19, 4.x before 4.1.9, and 4.2 before 4.2.1, it was possible to bypass validation when using one form field to upload multiple files. This multiple upload has never been supported by forms.FileField or forms.ImageField (only the last uploaded file was validated). However, Django's "Uploading multiple files" documentation suggested otherwise. Since, there is no ptest available for python3-django so have not tested the patch changes at runtime. Signed-off-by: Narpat Mali <narpat.mali@windriver.com> Signed-off-by: Armin Kuster <akuster808@gmail.com>
Diffstat (limited to 'meta-python/recipes-devtools/python/python3-django')
-rw-r--r--meta-python/recipes-devtools/python/python3-django/CVE-2023-31047.patch352
1 files changed, 352 insertions, 0 deletions
diff --git a/meta-python/recipes-devtools/python/python3-django/CVE-2023-31047.patch b/meta-python/recipes-devtools/python/python3-django/CVE-2023-31047.patch
new file mode 100644
index 0000000000..ab29a2ed97
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-django/CVE-2023-31047.patch
@@ -0,0 +1,352 @@
1From fd3215dec5d50aa1f09cb1f8eba193524e7379f3 Mon Sep 17 00:00:00 2001
2From: Mariusz Felisiak <felisiak.mariusz@gmail.com>
3Date: Thu, 25 May 2023 14:49:15 +0000
4Subject: [PATCH] Fixed CVE-2023-31047, Fixed #31710
5
6-- Prevented potential bypass of validation when uploading multiple files using one form field.
7
8Thanks Moataz Al-Sharida and nawaik for reports.
9
10Co-authored-by: Shai Berger <shai@platonix.com>
11Co-authored-by: nessita <124304+nessita@users.noreply.github.com>
12
13CVE: CVE-2023-31047
14
15Upstream-Status: Backport [https://github.com/django/django/commit/fb4c55d9ec4bb812a7fb91fa20510d91645e411b]
16
17Signed-off-by: Narpat Mali <narpat.mali@windriver.com>
18---
19 django/forms/widgets.py | 26 ++++++-
20 docs/releases/2.2.28.txt | 18 +++++
21 docs/topics/http/file-uploads.txt | 65 ++++++++++++++++--
22 .../forms_tests/field_tests/test_filefield.py | 68 ++++++++++++++++++-
23 .../widget_tests/test_clearablefileinput.py | 5 ++
24 .../widget_tests/test_fileinput.py | 44 ++++++++++++
25 6 files changed, 218 insertions(+), 8 deletions(-)
26
27diff --git a/django/forms/widgets.py b/django/forms/widgets.py
28index e37036c..d0cc131 100644
29--- a/django/forms/widgets.py
30+++ b/django/forms/widgets.py
31@@ -372,17 +372,41 @@ class MultipleHiddenInput(HiddenInput):
32
33
34 class FileInput(Input):
35+ allow_multiple_selected = False
36 input_type = 'file'
37 needs_multipart_form = True
38 template_name = 'django/forms/widgets/file.html'
39
40+ def __init__(self, attrs=None):
41+ if (
42+ attrs is not None
43+ and not self.allow_multiple_selected
44+ and attrs.get("multiple", False)
45+ ):
46+ raise ValueError(
47+ "%s doesn't support uploading multiple files."
48+ % self.__class__.__qualname__
49+ )
50+ if self.allow_multiple_selected:
51+ if attrs is None:
52+ attrs = {"multiple": True}
53+ else:
54+ attrs.setdefault("multiple", True)
55+ super().__init__(attrs)
56+
57 def format_value(self, value):
58 """File input never renders a value."""
59 return
60
61 def value_from_datadict(self, data, files, name):
62 "File widgets take data from FILES, not POST"
63- return files.get(name)
64+ getter = files.get
65+ if self.allow_multiple_selected:
66+ try:
67+ getter = files.getlist
68+ except AttributeError:
69+ pass
70+ return getter(name)
71
72 def value_omitted_from_data(self, data, files, name):
73 return name not in files
74diff --git a/docs/releases/2.2.28.txt b/docs/releases/2.2.28.txt
75index 43270fc..854c6b0 100644
76--- a/docs/releases/2.2.28.txt
77+++ b/docs/releases/2.2.28.txt
78@@ -20,3 +20,21 @@ CVE-2022-28347: Potential SQL injection via ``QuerySet.explain(**options)`` on P
79 :meth:`.QuerySet.explain` method was subject to SQL injection in option names,
80 using a suitably crafted dictionary, with dictionary expansion, as the
81 ``**options`` argument.
82+
83+Backporting the CVE-2023-31047 fix on Django 2.2.28.
84+
85+CVE-2023-31047: Potential bypass of validation when uploading multiple files using one form field
86+=================================================================================================
87+
88+Uploading multiple files using one form field has never been supported by
89+:class:`.forms.FileField` or :class:`.forms.ImageField` as only the last
90+uploaded file was validated. Unfortunately, :ref:`uploading_multiple_files`
91+topic suggested otherwise.
92+
93+In order to avoid the vulnerability, :class:`~django.forms.ClearableFileInput`
94+and :class:`~django.forms.FileInput` form widgets now raise ``ValueError`` when
95+the ``multiple`` HTML attribute is set on them. To prevent the exception and
96+keep the old behavior, set ``allow_multiple_selected`` to ``True``.
97+
98+For more details on using the new attribute and handling of multiple files
99+through a single field, see :ref:`uploading_multiple_files`.
100diff --git a/docs/topics/http/file-uploads.txt b/docs/topics/http/file-uploads.txt
101index 21a6f06..c1ffb80 100644
102--- a/docs/topics/http/file-uploads.txt
103+++ b/docs/topics/http/file-uploads.txt
104@@ -127,19 +127,54 @@ field in the model::
105 form = UploadFileForm()
106 return render(request, 'upload.html', {'form': form})
107
108+.. _uploading_multiple_files:
109+
110 Uploading multiple files
111 ------------------------
112
113-If you want to upload multiple files using one form field, set the ``multiple``
114-HTML attribute of field's widget:
115+..
116+ Tests in tests.forms_tests.field_tests.test_filefield.MultipleFileFieldTest
117+ should be updated after any changes in the following snippets.
118+
119+If you want to upload multiple files using one form field, create a subclass
120+of the field's widget and set the ``allow_multiple_selected`` attribute on it
121+to ``True``.
122+
123+In order for such files to be all validated by your form (and have the value of
124+the field include them all), you will also have to subclass ``FileField``. See
125+below for an example.
126+
127+.. admonition:: Multiple file field
128+
129+ Django is likely to have a proper multiple file field support at some point
130+ in the future.
131
132 .. code-block:: python
133 :caption: forms.py
134
135 from django import forms
136
137+
138+ class MultipleFileInput(forms.ClearableFileInput):
139+ allow_multiple_selected = True
140+
141+
142+ class MultipleFileField(forms.FileField):
143+ def __init__(self, *args, **kwargs):
144+ kwargs.setdefault("widget", MultipleFileInput())
145+ super().__init__(*args, **kwargs)
146+
147+ def clean(self, data, initial=None):
148+ single_file_clean = super().clean
149+ if isinstance(data, (list, tuple)):
150+ result = [single_file_clean(d, initial) for d in data]
151+ else:
152+ result = single_file_clean(data, initial)
153+ return result
154+
155+
156 class FileFieldForm(forms.Form):
157- file_field = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}))
158+ file_field = MultipleFileField()
159
160 Then override the ``post`` method of your
161 :class:`~django.views.generic.edit.FormView` subclass to handle multiple file
162@@ -159,14 +194,32 @@ uploads:
163 def post(self, request, *args, **kwargs):
164 form_class = self.get_form_class()
165 form = self.get_form(form_class)
166- files = request.FILES.getlist('file_field')
167 if form.is_valid():
168- for f in files:
169- ... # Do something with each file.
170 return self.form_valid(form)
171 else:
172 return self.form_invalid(form)
173
174+ def form_valid(self, form):
175+ files = form.cleaned_data["file_field"]
176+ for f in files:
177+ ... # Do something with each file.
178+ return super().form_valid()
179+
180+.. warning::
181+
182+ This will allow you to handle multiple files at the form level only. Be
183+ aware that you cannot use it to put multiple files on a single model
184+ instance (in a single field), for example, even if the custom widget is used
185+ with a form field related to a model ``FileField``.
186+
187+.. backportedfix:: 2.2.28
188+
189+ In previous versions, there was no support for the ``allow_multiple_selected``
190+ class attribute, and users were advised to create the widget with the HTML
191+ attribute ``multiple`` set through the ``attrs`` argument. However, this
192+ caused validation of the form field to be applied only to the last file
193+ submitted, which could have adverse security implications.
194+
195 Upload Handlers
196 ===============
197
198diff --git a/tests/forms_tests/field_tests/test_filefield.py b/tests/forms_tests/field_tests/test_filefield.py
199index 3357444..ba559ee 100644
200--- a/tests/forms_tests/field_tests/test_filefield.py
201+++ b/tests/forms_tests/field_tests/test_filefield.py
202@@ -1,7 +1,8 @@
203 import pickle
204
205 from django.core.files.uploadedfile import SimpleUploadedFile
206-from django.forms import FileField, ValidationError
207+from django.core.validators import validate_image_file_extension
208+from django.forms import FileField, FileInput, ValidationError
209 from django.test import SimpleTestCase
210
211
212@@ -82,3 +83,68 @@ class FileFieldTest(SimpleTestCase):
213
214 def test_file_picklable(self):
215 self.assertIsInstance(pickle.loads(pickle.dumps(FileField())), FileField)
216+
217+
218+class MultipleFileInput(FileInput):
219+ allow_multiple_selected = True
220+
221+
222+class MultipleFileField(FileField):
223+ def __init__(self, *args, **kwargs):
224+ kwargs.setdefault("widget", MultipleFileInput())
225+ super().__init__(*args, **kwargs)
226+
227+ def clean(self, data, initial=None):
228+ single_file_clean = super().clean
229+ if isinstance(data, (list, tuple)):
230+ result = [single_file_clean(d, initial) for d in data]
231+ else:
232+ result = single_file_clean(data, initial)
233+ return result
234+
235+
236+class MultipleFileFieldTest(SimpleTestCase):
237+ def test_file_multiple(self):
238+ f = MultipleFileField()
239+ files = [
240+ SimpleUploadedFile("name1", b"Content 1"),
241+ SimpleUploadedFile("name2", b"Content 2"),
242+ ]
243+ self.assertEqual(f.clean(files), files)
244+
245+ def test_file_multiple_empty(self):
246+ f = MultipleFileField()
247+ files = [
248+ SimpleUploadedFile("empty", b""),
249+ SimpleUploadedFile("nonempty", b"Some Content"),
250+ ]
251+ msg = "'The submitted file is empty.'"
252+ with self.assertRaisesMessage(ValidationError, msg):
253+ f.clean(files)
254+ with self.assertRaisesMessage(ValidationError, msg):
255+ f.clean(files[::-1])
256+
257+ def test_file_multiple_validation(self):
258+ f = MultipleFileField(validators=[validate_image_file_extension])
259+
260+ good_files = [
261+ SimpleUploadedFile("image1.jpg", b"fake JPEG"),
262+ SimpleUploadedFile("image2.png", b"faux image"),
263+ SimpleUploadedFile("image3.bmp", b"fraudulent bitmap"),
264+ ]
265+ self.assertEqual(f.clean(good_files), good_files)
266+
267+ evil_files = [
268+ SimpleUploadedFile("image1.sh", b"#!/bin/bash -c 'echo pwned!'\n"),
269+ SimpleUploadedFile("image2.png", b"faux image"),
270+ SimpleUploadedFile("image3.jpg", b"fake JPEG"),
271+ ]
272+
273+ evil_rotations = (
274+ evil_files[i:] + evil_files[:i] # Rotate by i.
275+ for i in range(len(evil_files))
276+ )
277+ msg = "File extension “sh” is not allowed. Allowed extensions are: "
278+ for rotated_evil_files in evil_rotations:
279+ with self.assertRaisesMessage(ValidationError, msg):
280+ f.clean(rotated_evil_files)
281diff --git a/tests/forms_tests/widget_tests/test_clearablefileinput.py b/tests/forms_tests/widget_tests/test_clearablefileinput.py
282index 2ba376d..8d9e38a 100644
283--- a/tests/forms_tests/widget_tests/test_clearablefileinput.py
284+++ b/tests/forms_tests/widget_tests/test_clearablefileinput.py
285@@ -161,3 +161,8 @@ class ClearableFileInputTest(WidgetTest):
286 self.assertIs(widget.value_omitted_from_data({}, {}, 'field'), True)
287 self.assertIs(widget.value_omitted_from_data({}, {'field': 'x'}, 'field'), False)
288 self.assertIs(widget.value_omitted_from_data({'field-clear': 'y'}, {}, 'field'), False)
289+
290+ def test_multiple_error(self):
291+ msg = "ClearableFileInput doesn't support uploading multiple files."
292+ with self.assertRaisesMessage(ValueError, msg):
293+ ClearableFileInput(attrs={"multiple": True})
294diff --git a/tests/forms_tests/widget_tests/test_fileinput.py b/tests/forms_tests/widget_tests/test_fileinput.py
295index bbd7c7f..24daf5d 100644
296--- a/tests/forms_tests/widget_tests/test_fileinput.py
297+++ b/tests/forms_tests/widget_tests/test_fileinput.py
298@@ -1,4 +1,6 @@
299+from django.core.files.uploadedfile import SimpleUploadedFile
300 from django.forms import FileInput
301+from django.utils.datastructures import MultiValueDict
302
303 from .base import WidgetTest
304
305@@ -18,3 +20,45 @@ class FileInputTest(WidgetTest):
306 def test_value_omitted_from_data(self):
307 self.assertIs(self.widget.value_omitted_from_data({}, {}, 'field'), True)
308 self.assertIs(self.widget.value_omitted_from_data({}, {'field': 'value'}, 'field'), False)
309+
310+ def test_multiple_error(self):
311+ msg = "FileInput doesn't support uploading multiple files."
312+ with self.assertRaisesMessage(ValueError, msg):
313+ FileInput(attrs={"multiple": True})
314+
315+ def test_value_from_datadict_multiple(self):
316+ class MultipleFileInput(FileInput):
317+ allow_multiple_selected = True
318+
319+ file_1 = SimpleUploadedFile("something1.txt", b"content 1")
320+ file_2 = SimpleUploadedFile("something2.txt", b"content 2")
321+ # Uploading multiple files is allowed.
322+ widget = MultipleFileInput(attrs={"multiple": True})
323+ value = widget.value_from_datadict(
324+ data={"name": "Test name"},
325+ files=MultiValueDict({"myfile": [file_1, file_2]}),
326+ name="myfile",
327+ )
328+ self.assertEqual(value, [file_1, file_2])
329+ # Uploading multiple files is not allowed.
330+ widget = FileInput()
331+ value = widget.value_from_datadict(
332+ data={"name": "Test name"},
333+ files=MultiValueDict({"myfile": [file_1, file_2]}),
334+ name="myfile",
335+ )
336+ self.assertEqual(value, file_2)
337+
338+ def test_multiple_default(self):
339+ class MultipleFileInput(FileInput):
340+ allow_multiple_selected = True
341+
342+ tests = [
343+ (None, True),
344+ ({"class": "myclass"}, True),
345+ ({"multiple": False}, False),
346+ ]
347+ for attrs, expected in tests:
348+ with self.subTest(attrs=attrs):
349+ widget = MultipleFileInput(attrs=attrs)
350+ self.assertIs(widget.attrs["multiple"], expected)
351--
3522.40.0