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

Skip to content

Commit 8d2f7cf

Browse files
apollo13carltongibson
authored andcommitted
[3.2.x] Fixed CVE-2021-45452 -- Fixed potential path traversal in storage subsystem.
Thanks to Dennis Brinkrolf for the report.
1 parent c7fe895 commit 8d2f7cf

File tree

5 files changed

+45
-7
lines changed

5 files changed

+45
-7
lines changed

django/core/files/storage.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@ def save(self, name, content, max_length=None):
5151
content = File(content, name)
5252

5353
name = self.get_available_name(name, max_length=max_length)
54-
return self._save(name, content)
54+
name = self._save(name, content)
55+
# Ensure that the name returned from the storage system is still valid.
56+
validate_file_name(name, allow_relative_path=True)
57+
return name
5558

5659
# These methods are part of the public API, with default implementations.
5760

@@ -75,6 +78,7 @@ def get_available_name(self, name, max_length=None):
7578
Return a filename that's free on the target storage system and
7679
available for new content to be written to.
7780
"""
81+
name = str(name).replace('\\', '/')
7882
dir_name, file_name = os.path.split(name)
7983
if '..' in pathlib.PurePath(dir_name).parts:
8084
raise SuspiciousFileOperation("Detected path traversal attempt in '%s'" % dir_name)
@@ -108,6 +112,7 @@ def generate_filename(self, filename):
108112
Validate the filename by calling get_valid_name() and return a filename
109113
to be passed to the save() method.
110114
"""
115+
filename = str(filename).replace('\\', '/')
111116
# `filename` may include a path as returned by FileField.upload_to.
112117
dirname, filename = os.path.split(filename)
113118
if '..' in pathlib.PurePath(dirname).parts:
@@ -297,6 +302,8 @@ def _save(self, name, content):
297302
if self.file_permissions_mode is not None:
298303
os.chmod(full_path, self.file_permissions_mode)
299304

305+
# Ensure the saved path is always relative to the storage root.
306+
name = os.path.relpath(full_path, self.location)
300307
# Store filenames with forward slashes, even on Windows.
301308
return str(name).replace('\\', '/')
302309

docs/releases/2.2.26.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,12 @@ As a reminder, all untrusted user input should be validated before use.
3636

3737
This issue has severity "low" according to the :ref:`Django security policy
3838
<security-disclosure>`.
39+
40+
CVE-2021-45452: Potential directory-traversal via ``Storage.save()``
41+
====================================================================
42+
43+
``Storage.save()`` allowed directory-traversal if directly passed suitably
44+
crafted file names.
45+
46+
This issue has severity "low" according to the :ref:`Django security policy
47+
<security-disclosure>`.

docs/releases/3.2.11.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,12 @@ As a reminder, all untrusted user input should be validated before use.
3636

3737
This issue has severity "low" according to the :ref:`Django security policy
3838
<security-disclosure>`.
39+
40+
CVE-2021-45452: Potential directory-traversal via ``Storage.save()``
41+
====================================================================
42+
43+
``Storage.save()`` allowed directory-traversal if directly passed suitably
44+
crafted file names.
45+
46+
This issue has severity "low" according to the :ref:`Django security policy
47+
<security-disclosure>`.

tests/file_storage/test_generate_filename.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,20 @@ def test_storage_dangerous_paths(self):
5353
s.generate_filename(file_name)
5454

5555
def test_storage_dangerous_paths_dir_name(self):
56-
file_name = '/tmp/../path'
56+
candidates = [
57+
('tmp/../path', 'tmp/..'),
58+
('tmp\\..\\path', 'tmp/..'),
59+
('/tmp/../path', '/tmp/..'),
60+
('\\tmp\\..\\path', '/tmp/..'),
61+
]
5762
s = FileSystemStorage()
58-
msg = "Detected path traversal attempt in '/tmp/..'"
59-
with self.assertRaisesMessage(SuspiciousFileOperation, msg):
60-
s.get_available_name(file_name)
61-
with self.assertRaisesMessage(SuspiciousFileOperation, msg):
62-
s.generate_filename(file_name)
63+
for file_name, path in candidates:
64+
msg = "Detected path traversal attempt in '%s'" % path
65+
with self.subTest(file_name=file_name):
66+
with self.assertRaisesMessage(SuspiciousFileOperation, msg):
67+
s.get_available_name(file_name)
68+
with self.assertRaisesMessage(SuspiciousFileOperation, msg):
69+
s.generate_filename(file_name)
6370

6471
def test_filefield_dangerous_filename(self):
6572
candidates = [

tests/file_storage/tests.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,12 @@ def test_file_save_with_path(self):
297297

298298
self.storage.delete('path/to/test.file')
299299

300+
def test_file_save_abs_path(self):
301+
test_name = 'path/to/test.file'
302+
f = ContentFile('file saved with path')
303+
f_name = self.storage.save(os.path.join(self.temp_dir, test_name), f)
304+
self.assertEqual(f_name, test_name)
305+
300306
def test_save_doesnt_close(self):
301307
with TemporaryUploadedFile('test', 'text/plain', 1, 'utf8') as file:
302308
file.write(b'1')

0 commit comments

Comments
 (0)