From bd062445cffd3f6cc6dcd20d13e2abed818fa173 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 20 Jul 2022 12:14:45 +0200 Subject: [PATCH] Fixed CVE-2022-36359 -- Escaped filename in Content-Disposition header. Thanks to Motoyasu Saburi for the report. --- django/http/response.py | 4 +++- docs/releases/3.2.15.txt | 8 ++++++- docs/releases/4.0.7.txt | 8 ++++++- tests/responses/test_fileresponse.py | 35 ++++++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/django/http/response.py b/django/http/response.py index 2bcd549f345..7a0dd688f7b 100644 --- a/django/http/response.py +++ b/django/http/response.py @@ -575,7 +575,9 @@ class FileResponse(StreamingHttpResponse): disposition = "attachment" if self.as_attachment else "inline" try: filename.encode("ascii") - file_expr = 'filename="{}"'.format(filename) + file_expr = 'filename="{}"'.format( + filename.replace("\\", "\\\\").replace('"', r"\"") + ) except UnicodeEncodeError: file_expr = "filename*=utf-8''{}".format(quote(filename)) self.headers["Content-Disposition"] = "{}; {}".format( diff --git a/docs/releases/3.2.15.txt b/docs/releases/3.2.15.txt index a7a56ae965a..281444ecf2a 100644 --- a/docs/releases/3.2.15.txt +++ b/docs/releases/3.2.15.txt @@ -6,4 +6,10 @@ Django 3.2.15 release notes Django 3.2.15 fixes a security issue with severity "high" in 3.2.14. -... +CVE-2022-36359: Potential reflected file download vulnerability in ``FileResponse`` +=================================================================================== + +An application may have been vulnerable to a reflected file download (RFD) +attack that sets the Content-Disposition header of a +:class:`~django.http.FileResponse` when the ``filename`` was derived from +user-supplied input. The ``filename`` is now escaped to avoid this possibility. diff --git a/docs/releases/4.0.7.txt b/docs/releases/4.0.7.txt index 919f3520de3..7fb85550777 100644 --- a/docs/releases/4.0.7.txt +++ b/docs/releases/4.0.7.txt @@ -6,4 +6,10 @@ Django 4.0.7 release notes Django 4.0.7 fixes a security issue with severity "high" in 4.0.6. -... +CVE-2022-36359: Potential reflected file download vulnerability in ``FileResponse`` +=================================================================================== + +An application may have been vulnerable to a reflected file download (RFD) +attack that sets the Content-Disposition header of a +:class:`~django.http.FileResponse` when the ``filename`` was derived from +user-supplied input. The ``filename`` is now escaped to avoid this possibility. diff --git a/tests/responses/test_fileresponse.py b/tests/responses/test_fileresponse.py index af90b1170d8..952fe4dd7c5 100644 --- a/tests/responses/test_fileresponse.py +++ b/tests/responses/test_fileresponse.py @@ -143,6 +143,41 @@ class FileResponseTests(SimpleTestCase): '%s; filename="%s"' % (header_disposition, header_filename), ) + def test_content_disposition_escaping(self): + # fmt: off + tests = [ + ( + 'multi-part-one";\" dummy".txt', + r"multi-part-one\";\" dummy\".txt" + ), + ] + # fmt: on + # Non-escape sequence backslashes are path segments on Windows, and are + # eliminated by an os.path.basename() check in FileResponse. + if sys.platform != "win32": + # fmt: off + tests += [ + ( + 'multi-part-one\\";\" dummy".txt', + r"multi-part-one\\\";\" dummy\".txt" + ), + ( + 'multi-part-one\\";\\\" dummy".txt', + r"multi-part-one\\\";\\\" dummy\".txt" + ) + ] + # fmt: on + for filename, escaped in tests: + with self.subTest(filename=filename, escaped=escaped): + response = FileResponse( + io.BytesIO(b"binary content"), filename=filename, as_attachment=True + ) + response.close() + self.assertEqual( + response.headers["Content-Disposition"], + f'attachment; filename="{escaped}"', + ) + def test_content_disposition_buffer(self): response = FileResponse(io.BytesIO(b"binary content")) self.assertFalse(response.has_header("Content-Disposition"))