+36
-7
lines changedFilter options
+36
-7
lines changed Original file line number Diff line number Diff line change
@@ -51,7 +51,10 @@ def save(self, name, content, max_length=None):
51
51
content = File(content, name)
52
52
53
53
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
55
58
56
59
# These methods are part of the public API, with default implementations.
57
60
@@ -67,6 +70,7 @@ def get_available_name(self, name, max_length=None):
67
70
Return a filename that's free on the target storage system and
68
71
available for new content to be written to.
69
72
"""
73
+
name = str(name).replace('\\', '/')
70
74
dir_name, file_name = os.path.split(name)
71
75
if '..' in pathlib.PurePath(dir_name).parts:
72
76
raise SuspiciousFileOperation("Detected path traversal attempt in '%s'" % dir_name)
@@ -101,6 +105,7 @@ def generate_filename(self, filename):
101
105
Validate the filename by calling get_valid_name() and return a filename
102
106
to be passed to the save() method.
103
107
"""
108
+
filename = str(filename).replace('\\', '/')
104
109
# `filename` may include a path as returned by FileField.upload_to.
105
110
dirname, filename = os.path.split(filename)
106
111
if '..' in pathlib.PurePath(dirname).parts:
@@ -296,6 +301,8 @@ def _save(self, name, content):
296
301
if self.file_permissions_mode is not None:
297
302
os.chmod(full_path, self.file_permissions_mode)
298
303
304
+
# Ensure the saved path is always relative to the storage root.
305
+
name = os.path.relpath(full_path, self.location)
299
306
# Store filenames with forward slashes, even on Windows.
300
307
return name.replace('\\', '/')
301
308
Original file line number Diff line number Diff line change
@@ -36,3 +36,12 @@ As a reminder, all untrusted user input should be validated before use.
36
36
37
37
This issue has severity "low" according to the :ref:`Django security policy
38
38
<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>`.
Original file line number Diff line number Diff line change
@@ -53,13 +53,20 @@ def test_storage_dangerous_paths(self):
53
53
s.generate_filename(file_name)
54
54
55
55
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
+
]
57
62
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)
63
70
64
71
def test_filefield_dangerous_filename(self):
65
72
candidates = [
Original file line number Diff line number Diff line change
@@ -291,6 +291,12 @@ def test_file_save_with_path(self):
291
291
292
292
self.storage.delete('path/to/test.file')
293
293
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
+
294
300
def test_save_doesnt_close(self):
295
301
with TemporaryUploadedFile('test', 'text/plain', 1, 'utf8') as file:
296
302
file.write(b'1')
You can’t perform that action at this time.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4