Merge pull request #211 from tari/196-rfd

Guard against reflected file download
This commit is contained in:
Rémy HUBSCHER 2024-08-05 10:26:15 +02:00 committed by GitHub
commit 18cb41f760
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 26 additions and 4 deletions

View file

@ -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 (

View file

@ -1,4 +1,5 @@
"""Tests around project's distribution and packaging."""
import importlib.metadata
import os
import unittest

View file

@ -19,3 +19,16 @@ class DownloadResponseTestCase(unittest.TestCase):
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"]
)

View file

@ -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 =
@ -76,3 +76,4 @@ source = django_downloadview,demo
[pytest]
DJANGO_SETTINGS_MODULE = demoproject.settings
addopts = --doctest-modules --ignore=docs/
python_files = tests/*.py