Fixed CVE-2021-45452 -- Fixed potential path traversal in storage subsystem.
Thanks to Dennis Brinkrolf for the report.
This commit is contained in:
parent
761f449e0d
commit
6d343d01c5
|
@ -51,7 +51,10 @@ class Storage:
|
||||||
content = File(content, name)
|
content = File(content, name)
|
||||||
|
|
||||||
name = self.get_available_name(name, max_length=max_length)
|
name = self.get_available_name(name, max_length=max_length)
|
||||||
return self._save(name, content)
|
name = self._save(name, content)
|
||||||
|
# Ensure that the name returned from the storage system is still valid.
|
||||||
|
validate_file_name(name, allow_relative_path=True)
|
||||||
|
return name
|
||||||
|
|
||||||
# These methods are part of the public API, with default implementations.
|
# These methods are part of the public API, with default implementations.
|
||||||
|
|
||||||
|
@ -75,6 +78,7 @@ class Storage:
|
||||||
Return a filename that's free on the target storage system and
|
Return a filename that's free on the target storage system and
|
||||||
available for new content to be written to.
|
available for new content to be written to.
|
||||||
"""
|
"""
|
||||||
|
name = str(name).replace('\\', '/')
|
||||||
dir_name, file_name = os.path.split(name)
|
dir_name, file_name = os.path.split(name)
|
||||||
if '..' in pathlib.PurePath(dir_name).parts:
|
if '..' in pathlib.PurePath(dir_name).parts:
|
||||||
raise SuspiciousFileOperation("Detected path traversal attempt in '%s'" % dir_name)
|
raise SuspiciousFileOperation("Detected path traversal attempt in '%s'" % dir_name)
|
||||||
|
@ -108,6 +112,7 @@ class Storage:
|
||||||
Validate the filename by calling get_valid_name() and return a filename
|
Validate the filename by calling get_valid_name() and return a filename
|
||||||
to be passed to the save() method.
|
to be passed to the save() method.
|
||||||
"""
|
"""
|
||||||
|
filename = str(filename).replace('\\', '/')
|
||||||
# `filename` may include a path as returned by FileField.upload_to.
|
# `filename` may include a path as returned by FileField.upload_to.
|
||||||
dirname, filename = os.path.split(filename)
|
dirname, filename = os.path.split(filename)
|
||||||
if '..' in pathlib.PurePath(dirname).parts:
|
if '..' in pathlib.PurePath(dirname).parts:
|
||||||
|
@ -297,6 +302,8 @@ class FileSystemStorage(Storage):
|
||||||
if self.file_permissions_mode is not None:
|
if self.file_permissions_mode is not None:
|
||||||
os.chmod(full_path, self.file_permissions_mode)
|
os.chmod(full_path, self.file_permissions_mode)
|
||||||
|
|
||||||
|
# Ensure the saved path is always relative to the storage root.
|
||||||
|
name = os.path.relpath(full_path, self.location)
|
||||||
# Store filenames with forward slashes, even on Windows.
|
# Store filenames with forward slashes, even on Windows.
|
||||||
return str(name).replace('\\', '/')
|
return str(name).replace('\\', '/')
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,11 @@ resolution logic, that will not call methods, nor allow indexing on
|
||||||
dictionaries.
|
dictionaries.
|
||||||
|
|
||||||
As a reminder, all untrusted user input should be validated before use.
|
As a reminder, all untrusted user input should be validated before use.
|
||||||
|
CVE-2021-45452: Potential directory-traversal via ``Storage.save()``
|
||||||
|
====================================================================
|
||||||
|
|
||||||
|
``Storage.save()`` allowed directory-traversal if directly passed suitably
|
||||||
|
crafted file names.
|
||||||
|
|
||||||
This issue has severity "low" according to the :ref:`Django security policy
|
This issue has severity "low" according to the :ref:`Django security policy
|
||||||
<security-disclosure>`.
|
<security-disclosure>`.
|
||||||
|
|
|
@ -33,6 +33,11 @@ resolution logic, that will not call methods, nor allow indexing on
|
||||||
dictionaries.
|
dictionaries.
|
||||||
|
|
||||||
As a reminder, all untrusted user input should be validated before use.
|
As a reminder, all untrusted user input should be validated before use.
|
||||||
|
CVE-2021-45452: Potential directory-traversal via ``Storage.save()``
|
||||||
|
====================================================================
|
||||||
|
|
||||||
|
``Storage.save()`` allowed directory-traversal if directly passed suitably
|
||||||
|
crafted file names.
|
||||||
|
|
||||||
This issue has severity "low" according to the :ref:`Django security policy
|
This issue has severity "low" according to the :ref:`Django security policy
|
||||||
<security-disclosure>`.
|
<security-disclosure>`.
|
||||||
|
|
|
@ -33,6 +33,11 @@ resolution logic, that will not call methods, nor allow indexing on
|
||||||
dictionaries.
|
dictionaries.
|
||||||
|
|
||||||
As a reminder, all untrusted user input should be validated before use.
|
As a reminder, all untrusted user input should be validated before use.
|
||||||
|
CVE-2021-45452: Potential directory-traversal via ``Storage.save()``
|
||||||
|
====================================================================
|
||||||
|
|
||||||
|
``Storage.save()`` allowed directory-traversal if directly passed suitably
|
||||||
|
crafted file names.
|
||||||
|
|
||||||
This issue has severity "low" according to the :ref:`Django security policy
|
This issue has severity "low" according to the :ref:`Django security policy
|
||||||
<security-disclosure>`.
|
<security-disclosure>`.
|
||||||
|
|
|
@ -53,13 +53,20 @@ class GenerateFilenameStorageTests(SimpleTestCase):
|
||||||
s.generate_filename(file_name)
|
s.generate_filename(file_name)
|
||||||
|
|
||||||
def test_storage_dangerous_paths_dir_name(self):
|
def test_storage_dangerous_paths_dir_name(self):
|
||||||
file_name = '/tmp/../path'
|
candidates = [
|
||||||
|
('tmp/../path', 'tmp/..'),
|
||||||
|
('tmp\\..\\path', 'tmp/..'),
|
||||||
|
('/tmp/../path', '/tmp/..'),
|
||||||
|
('\\tmp\\..\\path', '/tmp/..'),
|
||||||
|
]
|
||||||
s = FileSystemStorage()
|
s = FileSystemStorage()
|
||||||
msg = "Detected path traversal attempt in '/tmp/..'"
|
for file_name, path in candidates:
|
||||||
with self.assertRaisesMessage(SuspiciousFileOperation, msg):
|
msg = "Detected path traversal attempt in '%s'" % path
|
||||||
s.get_available_name(file_name)
|
with self.subTest(file_name=file_name):
|
||||||
with self.assertRaisesMessage(SuspiciousFileOperation, msg):
|
with self.assertRaisesMessage(SuspiciousFileOperation, msg):
|
||||||
s.generate_filename(file_name)
|
s.get_available_name(file_name)
|
||||||
|
with self.assertRaisesMessage(SuspiciousFileOperation, msg):
|
||||||
|
s.generate_filename(file_name)
|
||||||
|
|
||||||
def test_filefield_dangerous_filename(self):
|
def test_filefield_dangerous_filename(self):
|
||||||
candidates = [
|
candidates = [
|
||||||
|
|
|
@ -298,6 +298,12 @@ class FileStorageTests(SimpleTestCase):
|
||||||
|
|
||||||
self.storage.delete('path/to/test.file')
|
self.storage.delete('path/to/test.file')
|
||||||
|
|
||||||
|
def test_file_save_abs_path(self):
|
||||||
|
test_name = 'path/to/test.file'
|
||||||
|
f = ContentFile('file saved with path')
|
||||||
|
f_name = self.storage.save(os.path.join(self.temp_dir, test_name), f)
|
||||||
|
self.assertEqual(f_name, test_name)
|
||||||
|
|
||||||
@unittest.skipUnless(symlinks_supported(), 'Must be able to symlink to run this test.')
|
@unittest.skipUnless(symlinks_supported(), 'Must be able to symlink to run this test.')
|
||||||
def test_file_save_broken_symlink(self):
|
def test_file_save_broken_symlink(self):
|
||||||
"""A new path is created on save when a broken symlink is supplied."""
|
"""A new path is created on save when a broken symlink is supplied."""
|
||||||
|
|
Loading…
Reference in New Issue