summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChen Qi <Qi.Chen@windriver.com>2021-04-23 11:53:57 +0800
committerArmin Kuster <akuster808@gmail.com>2021-07-05 14:54:40 -0700
commit958d8a5286172da53440b77305b785c57a98fe49 (patch)
tree1dfc3dce97e65ba13a5a75bc7c62f2bdf8fe531c
parentf01a9056a997bc180af0644bfc640d3e6ea5fb62 (diff)
downloadmeta-openembedded-958d8a5286172da53440b77305b785c57a98fe49.tar.gz
python3-django: upgrade to 2.2.20
2.2.x is LTS, so upgrade to latest release 2.2.20. This upgrade fixes several CVEs such as CVE-2021-3281. Also, CVE-2021-28658.patch is dropped as it's already in 2.2.20. Signed-off-by: Chen Qi <Qi.Chen@windriver.com> Signed-off-by: Khem Raj <raj.khem@gmail.com> Signed-off-by: Trevor Gamblin <trevor.gamblin@windriver.com> Signed-off-by: Armin Kuster <akuster808@gmail.com> (cherry picked from commit e705d4932a57d0dc3a961fed73ae5ad2e0313429) Signed-off-by: Armin Kuster <akuster808@gmail.com>
-rw-r--r--meta-python/recipes-devtools/python/python3-django-2.2.16/CVE-2021-28658.patch289
-rw-r--r--meta-python/recipes-devtools/python/python3-django_2.2.16.bb11
-rw-r--r--meta-python/recipes-devtools/python/python3-django_2.2.20.bb9
3 files changed, 9 insertions, 300 deletions
diff --git a/meta-python/recipes-devtools/python/python3-django-2.2.16/CVE-2021-28658.patch b/meta-python/recipes-devtools/python/python3-django-2.2.16/CVE-2021-28658.patch
deleted file mode 100644
index 325aa0042..000000000
--- a/meta-python/recipes-devtools/python/python3-django-2.2.16/CVE-2021-28658.patch
+++ /dev/null
@@ -1,289 +0,0 @@
1From 4036d62bda0e9e9f6172943794b744a454ca49c2 Mon Sep 17 00:00:00 2001
2From: Mariusz Felisiak <felisiak.mariusz@gmail.com>
3Date: Tue, 16 Mar 2021 10:19:00 +0100
4Subject: [PATCH] Fixed CVE-2021-28658 -- Fixed potential directory-traversal
5 via uploaded files.
6
7Thanks Claude Paroz for the initial patch.
8Thanks Dennis Brinkrolf for the report.
9
10Backport of d4d800ca1addc4141e03c5440a849bb64d1582cd from main.
11
12Upstream-Status: Backport
13CVE: CVE-2021-28658
14
15Reference to upstream patch:
16[https://github.com/django/django/commit/4036d62bda0e9e9f6172943794b744a454ca49c2]
17
18[SG: Adapted stable/2.2.x patch for 2.2.16]
19Signed-off-by: Stefan Ghinea <stefan.ghinea@windriver.com>
20---
21 django/http/multipartparser.py | 13 ++++--
22 docs/releases/2.2.16.txt | 12 +++++
23 tests/file_uploads/tests.py | 72 ++++++++++++++++++++++-------
24 tests/file_uploads/uploadhandler.py | 31 +++++++++++++
25 tests/file_uploads/urls.py | 1 +
26 tests/file_uploads/views.py | 12 ++++-
27 6 files changed, 120 insertions(+), 21 deletions(-)
28
29diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py
30index f6f12ca..5a9cca8 100644
31--- a/django/http/multipartparser.py
32+++ b/django/http/multipartparser.py
33@@ -7,6 +7,7 @@ file upload handlers for processing.
34 import base64
35 import binascii
36 import cgi
37+import os
38 from urllib.parse import unquote
39
40 from django.conf import settings
41@@ -205,7 +206,7 @@ class MultiPartParser:
42 file_name = disposition.get('filename')
43 if file_name:
44 file_name = force_text(file_name, encoding, errors='replace')
45- file_name = self.IE_sanitize(unescape_entities(file_name))
46+ file_name = self.sanitize_file_name(file_name)
47 if not file_name:
48 continue
49
50@@ -293,9 +294,13 @@ class MultiPartParser:
51 self._files.appendlist(force_text(old_field_name, self._encoding, errors='replace'), file_obj)
52 break
53
54- def IE_sanitize(self, filename):
55- """Cleanup filename from Internet Explorer full paths."""
56- return filename and filename[filename.rfind("\\") + 1:].strip()
57+ def sanitize_file_name(self, file_name):
58+ file_name = unescape_entities(file_name)
59+ # Cleanup Windows-style path separators.
60+ file_name = file_name[file_name.rfind('\\') + 1:].strip()
61+ return os.path.basename(file_name)
62+
63+ IE_sanitize = sanitize_file_name
64
65 def _close_files(self):
66 # Free up all file handles.
67diff --git a/docs/releases/2.2.16.txt b/docs/releases/2.2.16.txt
68index 31231fb..4b7021b 100644
69--- a/docs/releases/2.2.16.txt
70+++ b/docs/releases/2.2.16.txt
71@@ -2,6 +2,18 @@
72 Django 2.2.16 release notes
73 ===========================
74
75+*April 6, 2021*
76+
77+Backported from Django 2.2.20 a fix for a security issue.
78+
79+CVE-2021-28658: Potential directory-traversal via uploaded files
80+================================================================
81+
82+``MultiPartParser`` allowed directory-traversal via uploaded files with
83+suitably crafted file names.
84+
85+Built-in upload handlers were not affected by this vulnerability.
86+
87 *September 1, 2020*
88
89 Django 2.2.16 fixes two security issues and two data loss bugs in 2.2.15.
90diff --git a/tests/file_uploads/tests.py b/tests/file_uploads/tests.py
91index ea4976d..2a08d1b 100644
92--- a/tests/file_uploads/tests.py
93+++ b/tests/file_uploads/tests.py
94@@ -22,6 +22,21 @@ UNICODE_FILENAME = 'test-0123456789_中文_Orléans.jpg'
95 MEDIA_ROOT = sys_tempfile.mkdtemp()
96 UPLOAD_TO = os.path.join(MEDIA_ROOT, 'test_upload')
97
98+CANDIDATE_TRAVERSAL_FILE_NAMES = [
99+ '/tmp/hax0rd.txt', # Absolute path, *nix-style.
100+ 'C:\\Windows\\hax0rd.txt', # Absolute path, win-style.
101+ 'C:/Windows/hax0rd.txt', # Absolute path, broken-style.
102+ '\\tmp\\hax0rd.txt', # Absolute path, broken in a different way.
103+ '/tmp\\hax0rd.txt', # Absolute path, broken by mixing.
104+ 'subdir/hax0rd.txt', # Descendant path, *nix-style.
105+ 'subdir\\hax0rd.txt', # Descendant path, win-style.
106+ 'sub/dir\\hax0rd.txt', # Descendant path, mixed.
107+ '../../hax0rd.txt', # Relative path, *nix-style.
108+ '..\\..\\hax0rd.txt', # Relative path, win-style.
109+ '../..\\hax0rd.txt', # Relative path, mixed.
110+ '..&#x2F;hax0rd.txt', # HTML entities.
111+]
112+
113
114 @override_settings(MEDIA_ROOT=MEDIA_ROOT, ROOT_URLCONF='file_uploads.urls', MIDDLEWARE=[])
115 class FileUploadTests(TestCase):
116@@ -205,22 +220,8 @@ class FileUploadTests(TestCase):
117 # a malicious payload with an invalid file name (containing os.sep or
118 # os.pardir). This similar to what an attacker would need to do when
119 # trying such an attack.
120- scary_file_names = [
121- "/tmp/hax0rd.txt", # Absolute path, *nix-style.
122- "C:\\Windows\\hax0rd.txt", # Absolute path, win-style.
123- "C:/Windows/hax0rd.txt", # Absolute path, broken-style.
124- "\\tmp\\hax0rd.txt", # Absolute path, broken in a different way.
125- "/tmp\\hax0rd.txt", # Absolute path, broken by mixing.
126- "subdir/hax0rd.txt", # Descendant path, *nix-style.
127- "subdir\\hax0rd.txt", # Descendant path, win-style.
128- "sub/dir\\hax0rd.txt", # Descendant path, mixed.
129- "../../hax0rd.txt", # Relative path, *nix-style.
130- "..\\..\\hax0rd.txt", # Relative path, win-style.
131- "../..\\hax0rd.txt" # Relative path, mixed.
132- ]
133-
134 payload = client.FakePayload()
135- for i, name in enumerate(scary_file_names):
136+ for i, name in enumerate(CANDIDATE_TRAVERSAL_FILE_NAMES):
137 payload.write('\r\n'.join([
138 '--' + client.BOUNDARY,
139 'Content-Disposition: form-data; name="file%s"; filename="%s"' % (i, name),
140@@ -240,7 +241,7 @@ class FileUploadTests(TestCase):
141 response = self.client.request(**r)
142 # The filenames should have been sanitized by the time it got to the view.
143 received = response.json()
144- for i, name in enumerate(scary_file_names):
145+ for i, name in enumerate(CANDIDATE_TRAVERSAL_FILE_NAMES):
146 got = received["file%s" % i]
147 self.assertEqual(got, "hax0rd.txt")
148
149@@ -518,6 +519,36 @@ class FileUploadTests(TestCase):
150 # shouldn't differ.
151 self.assertEqual(os.path.basename(obj.testfile.path), 'MiXeD_cAsE.txt')
152
153+ def test_filename_traversal_upload(self):
154+ os.makedirs(UPLOAD_TO, exist_ok=True)
155+ self.addCleanup(shutil.rmtree, MEDIA_ROOT)
156+ file_name = '..&#x2F;test.txt',
157+ payload = client.FakePayload()
158+ payload.write(
159+ '\r\n'.join([
160+ '--' + client.BOUNDARY,
161+ 'Content-Disposition: form-data; name="my_file"; '
162+ 'filename="%s";' % file_name,
163+ 'Content-Type: text/plain',
164+ '',
165+ 'file contents.\r\n',
166+ '\r\n--' + client.BOUNDARY + '--\r\n',
167+ ]),
168+ )
169+ r = {
170+ 'CONTENT_LENGTH': len(payload),
171+ 'CONTENT_TYPE': client.MULTIPART_CONTENT,
172+ 'PATH_INFO': '/upload_traversal/',
173+ 'REQUEST_METHOD': 'POST',
174+ 'wsgi.input': payload,
175+ }
176+ response = self.client.request(**r)
177+ result = response.json()
178+ self.assertEqual(response.status_code, 200)
179+ self.assertEqual(result['file_name'], 'test.txt')
180+ self.assertIs(os.path.exists(os.path.join(MEDIA_ROOT, 'test.txt')), False)
181+ self.assertIs(os.path.exists(os.path.join(UPLOAD_TO, 'test.txt')), True)
182+
183
184 @override_settings(MEDIA_ROOT=MEDIA_ROOT)
185 class DirectoryCreationTests(SimpleTestCase):
186@@ -591,6 +622,15 @@ class MultiParserTests(SimpleTestCase):
187 }, StringIO('x'), [], 'utf-8')
188 self.assertEqual(multipart_parser._content_length, 0)
189
190+ def test_sanitize_file_name(self):
191+ parser = MultiPartParser({
192+ 'CONTENT_TYPE': 'multipart/form-data; boundary=_foo',
193+ 'CONTENT_LENGTH': '1'
194+ }, StringIO('x'), [], 'utf-8')
195+ for file_name in CANDIDATE_TRAVERSAL_FILE_NAMES:
196+ with self.subTest(file_name=file_name):
197+ self.assertEqual(parser.sanitize_file_name(file_name), 'hax0rd.txt')
198+
199 def test_rfc2231_parsing(self):
200 test_data = (
201 (b"Content-Type: application/x-stuff; title*=us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A",
202diff --git a/tests/file_uploads/uploadhandler.py b/tests/file_uploads/uploadhandler.py
203index 7c6199f..65d70c6 100644
204--- a/tests/file_uploads/uploadhandler.py
205+++ b/tests/file_uploads/uploadhandler.py
206@@ -1,6 +1,8 @@
207 """
208 Upload handlers to test the upload API.
209 """
210+import os
211+from tempfile import NamedTemporaryFile
212
213 from django.core.files.uploadhandler import FileUploadHandler, StopUpload
214
215@@ -35,3 +37,32 @@ class ErroringUploadHandler(FileUploadHandler):
216 """A handler that raises an exception."""
217 def receive_data_chunk(self, raw_data, start):
218 raise CustomUploadError("Oops!")
219+
220+
221+class TraversalUploadHandler(FileUploadHandler):
222+ """A handler with potential directory-traversal vulnerability."""
223+ def __init__(self, request=None):
224+ from .views import UPLOAD_TO
225+
226+ super().__init__(request)
227+ self.upload_dir = UPLOAD_TO
228+
229+ def file_complete(self, file_size):
230+ self.file.seek(0)
231+ self.file.size = file_size
232+ with open(os.path.join(self.upload_dir, self.file_name), 'wb') as fp:
233+ fp.write(self.file.read())
234+ return self.file
235+
236+ def new_file(
237+ self, field_name, file_name, content_type, content_length, charset=None,
238+ content_type_extra=None,
239+ ):
240+ super().new_file(
241+ file_name, file_name, content_length, content_length, charset,
242+ content_type_extra,
243+ )
244+ self.file = NamedTemporaryFile(suffix='.upload', dir=self.upload_dir)
245+
246+ def receive_data_chunk(self, raw_data, start):
247+ self.file.write(raw_data)
248diff --git a/tests/file_uploads/urls.py b/tests/file_uploads/urls.py
249index 3e7985d..eaac1da 100644
250--- a/tests/file_uploads/urls.py
251+++ b/tests/file_uploads/urls.py
252@@ -4,6 +4,7 @@ from . import views
253
254 urlpatterns = [
255 path('upload/', views.file_upload_view),
256+ path('upload_traversal/', views.file_upload_traversal_view),
257 path('verify/', views.file_upload_view_verify),
258 path('unicode_name/', views.file_upload_unicode_name),
259 path('echo/', views.file_upload_echo),
260diff --git a/tests/file_uploads/views.py b/tests/file_uploads/views.py
261index d4947e4..137c6f3 100644
262--- a/tests/file_uploads/views.py
263+++ b/tests/file_uploads/views.py
264@@ -6,7 +6,9 @@ from django.http import HttpResponse, HttpResponseServerError, JsonResponse
265
266 from .models import FileModel
267 from .tests import UNICODE_FILENAME, UPLOAD_TO
268-from .uploadhandler import ErroringUploadHandler, QuotaUploadHandler
269+from .uploadhandler import (
270+ ErroringUploadHandler, QuotaUploadHandler, TraversalUploadHandler,
271+)
272
273
274 def file_upload_view(request):
275@@ -158,3 +160,11 @@ def file_upload_fd_closing(request, access):
276 if access == 't':
277 request.FILES # Trigger file parsing.
278 return HttpResponse('')
279+
280+
281+def file_upload_traversal_view(request):
282+ request.upload_handlers.insert(0, TraversalUploadHandler())
283+ request.FILES # Trigger file parsing.
284+ return JsonResponse(
285+ {'file_name': request.upload_handlers[0].file_name},
286+ )
287--
2882.17.1
289
diff --git a/meta-python/recipes-devtools/python/python3-django_2.2.16.bb b/meta-python/recipes-devtools/python/python3-django_2.2.16.bb
deleted file mode 100644
index eb626e8d3..000000000
--- a/meta-python/recipes-devtools/python/python3-django_2.2.16.bb
+++ /dev/null
@@ -1,11 +0,0 @@
1require python-django.inc
2inherit setuptools3
3
4SRC_URI[md5sum] = "93faf5bbd54a19ea49f4932a813b9758"
5SRC_URI[sha256sum] = "62cf45e5ee425c52e411c0742e641a6588b7e8af0d2c274a27940931b2786594"
6
7RDEPENDS_${PN} += "\
8 ${PYTHON_PN}-sqlparse \
9"
10SRC_URI += "file://CVE-2021-28658.patch \
11"
diff --git a/meta-python/recipes-devtools/python/python3-django_2.2.20.bb b/meta-python/recipes-devtools/python/python3-django_2.2.20.bb
new file mode 100644
index 000000000..905d022a4
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-django_2.2.20.bb
@@ -0,0 +1,9 @@
1require python-django.inc
2inherit setuptools3
3
4SRC_URI[md5sum] = "947060d96ccc0a05e8049d839e541b25"
5SRC_URI[sha256sum] = "2569f9dc5f8e458a5e988b03d6b7a02bda59b006d6782f4ea0fd590ed7336a64"
6
7RDEPENDS_${PN} += "\
8 ${PYTHON_PN}-sqlparse \
9"