+45
-7
lines changedFilter options
+45
-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
@@ -75,6 +78,7 @@ def get_available_name(self, name, max_length=None):
75
78
Return a filename that's free on the target storage system and
76
79
available for new content to be written to.
77
80
"""
81
+
name = str(name).replace('\\', '/')
78
82
dir_name, file_name = os.path.split(name)
79
83
if '..' in pathlib.PurePath(dir_name).parts:
80
84
raise SuspiciousFileOperation("Detected path traversal attempt in '%s'" % dir_name)
@@ -108,6 +112,7 @@ def generate_filename(self, filename):
108
112
Validate the filename by calling get_valid_name() and return a filename
109
113
to be passed to the save() method.
110
114
"""
115
+
filename = str(filename).replace('\\', '/')
111
116
# `filename` may include a path as returned by FileField.upload_to.
112
117
dirname, filename = os.path.split(filename)
113
118
if '..' in pathlib.PurePath(dirname).parts:
@@ -297,6 +302,8 @@ def _save(self, name, content):
297
302
if self.file_permissions_mode is not None:
298
303
os.chmod(full_path, self.file_permissions_mode)
299
304
305
+
# Ensure the saved path is always relative to the storage root.
306
+
name = os.path.relpath(full_path, self.location)
300
307
# Store filenames with forward slashes, even on Windows.
301
308
return str(name).replace('\\', '/')
302
309
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
@@ -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
@@ -297,6 +297,12 @@ def test_file_save_with_path(self):
297
297
298
298
self.storage.delete('path/to/test.file')
299
299
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
+
300
306
def test_save_doesnt_close(self):
301
307
with TemporaryUploadedFile('test', 'text/plain', 1, 'utf8') as file:
302
308
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