From 0568c3c5593b302b9a7ef046775767d9eaa464b0 Mon Sep 17 00:00:00 2001 From: Peter Marheine Date: Tue, 30 Jul 2024 11:51:25 +0000 Subject: [PATCH] Prevent reflected file downloads on specially-named files This fixes #196, where it was observed that django_downloadview was vulnerable to reflected file download attacks with specially-named files, similar to CVE-2022-36359 in Django. This change adopts the same replacement rules as used in Django's fix in commit b3e4494d759202a3b6bf247fd34455bf13be5b80. --- django_downloadview/response.py | 9 ++++++++- tests/response.py | 13 +++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/django_downloadview/response.py b/django_downloadview/response.py index 3c9ae9d..6648aaa 100644 --- a/django_downloadview/response.py +++ b/django_downloadview/response.py @@ -72,9 +72,16 @@ def content_disposition(filename): """ if not filename: return "attachment" - ascii_filename = encode_basename_ascii(filename) + # ASCII filenames are quoted and must ensure escape sequences + # in the filename won't break out of the quoted header value + # which can permit a reflected file download attack. The UTF-8 + # version is immune because it's not quoted. + ascii_filename = ( + encode_basename_ascii(filename).replace("\\", "\\\\").replace('"', r'\"') + ) utf8_filename = encode_basename_utf8(filename) if ascii_filename == utf8_filename: # ASCII only. + return f'attachment; filename="{ascii_filename}"' else: return ( diff --git a/tests/response.py b/tests/response.py index d87ce2b..738d364 100644 --- a/tests/response.py +++ b/tests/response.py @@ -19,3 +19,16 @@ def test_content_disposition_encoding(self): self.assertIn( "filename*=UTF-8''espac%C3%A9%20.txt", headers["Content-Disposition"] ) + + def test_content_disposition_escaping(self): + """Content-Disposition headers escape special characters.""" + response = DownloadResponse( + "fake file", + attachment=True, + basename=r'"malicious\file.exe' + ) + headers = response.default_headers + self.assertIn( + r'filename="\"malicious\\file.exe"', + headers["Content-Disposition"] + ) \ No newline at end of file