Fixed CVE-2022-36359 -- Escaped filename in Content-Disposition header.
Thanks to Motoyasu Saburi for the report.
This commit is contained in:
parent
9062c23de8
commit
bd062445cf
|
@ -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(
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"))
|
||||
|
|
Loading…
Reference in New Issue