Skip to content

Commit b7d9529

Browse files
committed
[4.0.x] Fixed CVE-2022-36359 -- Escaped filename in Content-Disposition header.
Thanks to Motoyasu Saburi for the report.
1 parent 2eb7ded commit b7d9529

File tree

4 files changed

+52
-3
lines changed

4 files changed

+52
-3
lines changed

django/http/response.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,9 @@ def set_headers(self, filelike):
520520
disposition = "attachment" if self.as_attachment else "inline"
521521
try:
522522
filename.encode("ascii")
523-
file_expr = 'filename="{}"'.format(filename)
523+
file_expr = 'filename="{}"'.format(
524+
filename.replace("\\", "\\\\").replace('"', r"\"")
525+
)
524526
except UnicodeEncodeError:
525527
file_expr = "filename*=utf-8''{}".format(quote(filename))
526528
self.headers["Content-Disposition"] = "{}; {}".format(

docs/releases/3.2.15.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,10 @@ Django 3.2.15 release notes
66

77
Django 3.2.15 fixes a security issue with severity "high" in 3.2.14.
88

9-
...
9+
CVE-2022-36359: Potential reflected file download vulnerability in ``FileResponse``
10+
===================================================================================
11+
12+
An application may have been vulnerable to a reflected file download (RFD)
13+
attack that sets the Content-Disposition header of a
14+
:class:`~django.http.FileResponse` when the ``filename`` was derived from
15+
user-supplied input. The ``filename`` is now escaped to avoid this possibility.

docs/releases/4.0.7.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,10 @@ Django 4.0.7 release notes
66

77
Django 4.0.7 fixes a security issue with severity "high" in 4.0.6.
88

9-
...
9+
CVE-2022-36359: Potential reflected file download vulnerability in ``FileResponse``
10+
===================================================================================
11+
12+
An application may have been vulnerable to a reflected file download (RFD)
13+
attack that sets the Content-Disposition header of a
14+
:class:`~django.http.FileResponse` when the ``filename`` was derived from
15+
user-supplied input. The ``filename`` is now escaped to avoid this possibility.

tests/responses/test_fileresponse.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,38 @@ def test_repr(self):
101101
repr(response),
102102
'<FileResponse status_code=200, "application/octet-stream">',
103103
)
104+
105+
def test_content_disposition_escaping(self):
106+
# fmt: off
107+
tests = [
108+
(
109+
'multi-part-one";\" dummy".txt',
110+
r"multi-part-one\";\" dummy\".txt"
111+
),
112+
]
113+
# fmt: on
114+
# Non-escape sequence backslashes are path segments on Windows, and are
115+
# eliminated by an os.path.basename() check in FileResponse.
116+
if sys.platform != "win32":
117+
# fmt: off
118+
tests += [
119+
(
120+
'multi-part-one\\";\" dummy".txt',
121+
r"multi-part-one\\\";\" dummy\".txt"
122+
),
123+
(
124+
'multi-part-one\\";\\\" dummy".txt',
125+
r"multi-part-one\\\";\\\" dummy\".txt"
126+
)
127+
]
128+
# fmt: on
129+
for filename, escaped in tests:
130+
with self.subTest(filename=filename, escaped=escaped):
131+
response = FileResponse(
132+
io.BytesIO(b"binary content"), filename=filename, as_attachment=True
133+
)
134+
response.close()
135+
self.assertEqual(
136+
response.headers["Content-Disposition"],
137+
f'attachment; filename="{escaped}"',
138+
)

0 commit comments

Comments
 (0)