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

Skip to content

Commit 4cb35b3

Browse files
apollo13carltongibson
authored andcommitted
[2.2.x] Fixed CVE-2021-45452 -- Fixed potential path traversal in storage subsystem.
Thanks to Dennis Brinkrolf for the report.
1 parent c9f648c commit 4cb35b3

File tree

4 files changed

+36
-7
lines changed

4 files changed

+36
-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

@@ -67,6 +70,7 @@ def get_available_name(self, name, max_length=None):
6770
Return a filename that's free on the target storage system and
6871
available for new content to be written to.
6972
"""
73+
name = str(name).replace('\\', '/')
7074
dir_name, file_name = os.path.split(name)
7175
if '..' in pathlib.PurePath(dir_name).parts:
7276
raise SuspiciousFileOperation("Detected path traversal attempt in '%s'" % dir_name)
@@ -101,6 +105,7 @@ def generate_filename(self, filename):
101105
Validate the filename by calling get_valid_name() and return a filename
102106
to be passed to the save() method.
103107
"""
108+
filename = str(filename).replace('\\', '/')
104109
# `filename` may include a path as returned by FileField.upload_to.
105110
dirname, filename = os.path.split(filename)
106111
if '..' in pathlib.PurePath(dirname).parts:
@@ -296,6 +301,8 @@ def _save(self, name, content):
296301
if self.file_permissions_mode is not None:
297302
os.chmod(full_path, self.file_permissions_mode)
298303

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

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>`.

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
@@ -291,6 +291,12 @@ def test_file_save_with_path(self):
291291

292292
self.storage.delete('path/to/test.file')
293293

294+
def test_file_save_abs_path(self):
295+
test_name = 'path/to/test.file'
296+
f = ContentFile('file saved with path')
297+
f_name = self.storage.save(os.path.join(self.temp_dir, test_name), f)
298+
self.assertEqual(f_name, test_name)
299+
294300
def test_save_doesnt_close(self):
295301
with TemporaryUploadedFile('test', 'text/plain', 1, 'utf8') as file:
296302
file.write(b'1')

0 commit comments

Comments
 (0)