Skip to content

Commit

Permalink
Merge pull request #211 from tari/196-rfd
Browse files Browse the repository at this point in the history
Guard against reflected file download
  • Loading branch information
Natim authored Aug 5, 2024
2 parents 1b294f0 + e7e25e6 commit 18cb41f
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 4 deletions.
9 changes: 8 additions & 1 deletion django_downloadview/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
1 change: 1 addition & 0 deletions tests/packaging.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Tests around project's distribution and packaging."""
import importlib.metadata
import os
import unittest

Expand Down
13 changes: 13 additions & 0 deletions tests/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
)
7 changes: 4 additions & 3 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ deps =
commands =
pip install -e .
pip install -e demo
# doctests
# doctests and unit tests
pytest --cov=django_downloadview --cov=demoproject {posargs}
# all other test cases
coverage run --append {envbindir}/demo test {posargs: tests demoproject}
# demo project integration tests
coverage run --append {envbindir}/demo test {posargs: demoproject}
coverage xml
pip freeze
ignore_outcome =
Expand Down Expand Up @@ -76,3 +76,4 @@ source = django_downloadview,demo
[pytest]
DJANGO_SETTINGS_MODULE = demoproject.settings
addopts = --doctest-modules --ignore=docs/
python_files = tests/*.py

0 comments on commit 18cb41f

Please sign in to comment.