summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarlton Gibson <carlton.gibson@noumenal.es>2022-07-27 10:27:42 +0200
committerCarlton Gibson <carlton.gibson@noumenal.es>2022-08-03 08:48:00 +0200
commitb7d9529cbe0af4adabb6ea5d01ed8dcce3668fb3 (patch)
tree4aa0035fdcf98341989045b21967255e35be9070
parent2eb7dedd8f40f911d581d4e7619c046cefe203ce (diff)
downloaddjango-b7d9529cbe0af4adabb6ea5d01ed8dcce3668fb3.tar.gz
[4.0.x] Fixed CVE-2022-36359 -- Escaped filename in Content-Disposition header.
Thanks to Motoyasu Saburi for the report.
-rw-r--r--django/http/response.py4
-rw-r--r--docs/releases/3.2.15.txt8
-rw-r--r--docs/releases/4.0.7.txt8
-rw-r--r--tests/responses/test_fileresponse.py35
4 files changed, 52 insertions, 3 deletions
diff --git a/django/http/response.py b/django/http/response.py
index 7fbec9d223..aa76305dbe 100644
--- a/django/http/response.py
+++ b/django/http/response.py
@@ -520,7 +520,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 a7a56ae965..281444ecf2 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 919f3520de..7fb8555077 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 f73eb7f19d..77fa440c08 100644
--- a/tests/responses/test_fileresponse.py
+++ b/tests/responses/test_fileresponse.py
@@ -101,3 +101,38 @@ class FileResponseTests(SimpleTestCase):
repr(response),
'<FileResponse status_code=200, "application/octet-stream">',
)
+
+ 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}"',
+ )