Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b3e4494

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

File tree

3 files changed

+45
-2
lines changed

3 files changed

+45
-2
lines changed

django/http/response.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,9 @@ def set_headers(self, filelike):
485485
disposition = 'attachment' if self.as_attachment else 'inline'
486486
try:
487487
filename.encode('ascii')
488-
file_expr = 'filename="{}"'.format(filename)
488+
file_expr = 'filename="{}"'.format(
489+
filename.replace('\\', '\\\\').replace('"', r'\"')
490+
)
489491
except UnicodeEncodeError:
490492
file_expr = "filename*=utf-8''{}".format(quote(filename))
491493
self.headers['Content-Disposition'] = '{}; {}'.format(disposition, file_expr)

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.

tests/responses/test_fileresponse.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,38 @@ def test_unicode_attachment(self):
8989
response.headers['Content-Disposition'],
9090
"attachment; filename*=utf-8''%E7%A5%9D%E6%82%A8%E5%B9%B3%E5%AE%89.odt"
9191
)
92+
93+
def test_content_disposition_escaping(self):
94+
# fmt: off
95+
tests = [
96+
(
97+
'multi-part-one";\" dummy".txt',
98+
r"multi-part-one\";\" dummy\".txt"
99+
),
100+
]
101+
# fmt: on
102+
# Non-escape sequence backslashes are path segments on Windows, and are
103+
# eliminated by an os.path.basename() check in FileResponse.
104+
if sys.platform != "win32":
105+
# fmt: off
106+
tests += [
107+
(
108+
'multi-part-one\\";\" dummy".txt',
109+
r"multi-part-one\\\";\" dummy\".txt"
110+
),
111+
(
112+
'multi-part-one\\";\\\" dummy".txt',
113+
r"multi-part-one\\\";\\\" dummy\".txt"
114+
)
115+
]
116+
# fmt: on
117+
for filename, escaped in tests:
118+
with self.subTest(filename=filename, escaped=escaped):
119+
response = FileResponse(
120+
io.BytesIO(b"binary content"), filename=filename, as_attachment=True
121+
)
122+
response.close()
123+
self.assertEqual(
124+
response.headers["Content-Disposition"],
125+
f'attachment; filename="{escaped}"',
126+
)

0 commit comments

Comments
 (0)