Fixed CVE-2020-24583, #31921 -- Fixed permissions on intermediate-level static and storage directories on Python 3.7+.
Thanks WhiteSage for the report.
This commit is contained in:
parent
2bc38bc7ca
commit
8d7271578d
|
@ -237,9 +237,9 @@ class FileSystemStorage(Storage):
|
||||||
directory = os.path.dirname(full_path)
|
directory = os.path.dirname(full_path)
|
||||||
try:
|
try:
|
||||||
if self.directory_permissions_mode is not None:
|
if self.directory_permissions_mode is not None:
|
||||||
# os.makedirs applies the global umask, so we reset it,
|
# Set the umask because os.makedirs() doesn't apply the "mode"
|
||||||
# for consistency with file_permissions_mode behavior.
|
# argument to intermediate-level directories.
|
||||||
old_umask = os.umask(0)
|
old_umask = os.umask(0o777 & ~self.directory_permissions_mode)
|
||||||
try:
|
try:
|
||||||
os.makedirs(directory, self.directory_permissions_mode, exist_ok=True)
|
os.makedirs(directory, self.directory_permissions_mode, exist_ok=True)
|
||||||
finally:
|
finally:
|
||||||
|
|
|
@ -4,7 +4,18 @@ Django 2.2.16 release notes
|
||||||
|
|
||||||
*Expected September 1, 2020*
|
*Expected September 1, 2020*
|
||||||
|
|
||||||
Django 2.2.16 fixes two data loss bugs in 2.2.15.
|
Django 2.2.16 fixes a security issue and two data loss bugs in 2.2.15.
|
||||||
|
|
||||||
|
CVE-2020-24583: Incorrect permissions on intermediate-level directories on Python 3.7+
|
||||||
|
======================================================================================
|
||||||
|
|
||||||
|
On Python 3.7+, :setting:`FILE_UPLOAD_DIRECTORY_PERMISSIONS` mode was not
|
||||||
|
applied to intermediate-level directories created in the process of uploading
|
||||||
|
files and to intermediate-level collected static directories when using the
|
||||||
|
:djadmin:`collectstatic` management command.
|
||||||
|
|
||||||
|
You should review and manually fix permissions on existing intermediate-level
|
||||||
|
directories.
|
||||||
|
|
||||||
Bugfixes
|
Bugfixes
|
||||||
========
|
========
|
||||||
|
|
|
@ -4,7 +4,18 @@ Django 3.0.10 release notes
|
||||||
|
|
||||||
*Expected September 1, 2020*
|
*Expected September 1, 2020*
|
||||||
|
|
||||||
Django 3.0.10 fixes two data loss bugs in 3.0.9.
|
Django 3.0.10 fixes a security issue and two data loss bugs in 3.0.9.
|
||||||
|
|
||||||
|
CVE-2020-24583: Incorrect permissions on intermediate-level directories on Python 3.7+
|
||||||
|
======================================================================================
|
||||||
|
|
||||||
|
On Python 3.7+, :setting:`FILE_UPLOAD_DIRECTORY_PERMISSIONS` mode was not
|
||||||
|
applied to intermediate-level directories created in the process of uploading
|
||||||
|
files and to intermediate-level collected static directories when using the
|
||||||
|
:djadmin:`collectstatic` management command.
|
||||||
|
|
||||||
|
You should review and manually fix permissions on existing intermediate-level
|
||||||
|
directories.
|
||||||
|
|
||||||
Bugfixes
|
Bugfixes
|
||||||
========
|
========
|
||||||
|
|
|
@ -4,7 +4,18 @@ Django 3.1.1 release notes
|
||||||
|
|
||||||
*Expected September 1, 2020*
|
*Expected September 1, 2020*
|
||||||
|
|
||||||
Django 3.1.1 fixes several bugs in 3.1.
|
Django 3.1.1 fixes a security issue and several bugs in 3.1.
|
||||||
|
|
||||||
|
CVE-2020-24583: Incorrect permissions on intermediate-level directories on Python 3.7+
|
||||||
|
======================================================================================
|
||||||
|
|
||||||
|
On Python 3.7+, :setting:`FILE_UPLOAD_DIRECTORY_PERMISSIONS` mode was not
|
||||||
|
applied to intermediate-level directories created in the process of uploading
|
||||||
|
files and to intermediate-level collected static directories when using the
|
||||||
|
:djadmin:`collectstatic` management command.
|
||||||
|
|
||||||
|
You should review and manually fix permissions on existing intermediate-level
|
||||||
|
directories.
|
||||||
|
|
||||||
Bugfixes
|
Bugfixes
|
||||||
========
|
========
|
||||||
|
|
|
@ -972,16 +972,19 @@ class FileStoragePermissions(unittest.TestCase):
|
||||||
@override_settings(FILE_UPLOAD_DIRECTORY_PERMISSIONS=0o765)
|
@override_settings(FILE_UPLOAD_DIRECTORY_PERMISSIONS=0o765)
|
||||||
def test_file_upload_directory_permissions(self):
|
def test_file_upload_directory_permissions(self):
|
||||||
self.storage = FileSystemStorage(self.storage_dir)
|
self.storage = FileSystemStorage(self.storage_dir)
|
||||||
name = self.storage.save("the_directory/the_file", ContentFile("data"))
|
name = self.storage.save('the_directory/subdir/the_file', ContentFile('data'))
|
||||||
dir_mode = os.stat(os.path.dirname(self.storage.path(name)))[0] & 0o777
|
file_path = Path(self.storage.path(name))
|
||||||
self.assertEqual(dir_mode, 0o765)
|
self.assertEqual(file_path.parent.stat().st_mode & 0o777, 0o765)
|
||||||
|
self.assertEqual(file_path.parent.parent.stat().st_mode & 0o777, 0o765)
|
||||||
|
|
||||||
@override_settings(FILE_UPLOAD_DIRECTORY_PERMISSIONS=None)
|
@override_settings(FILE_UPLOAD_DIRECTORY_PERMISSIONS=None)
|
||||||
def test_file_upload_directory_default_permissions(self):
|
def test_file_upload_directory_default_permissions(self):
|
||||||
self.storage = FileSystemStorage(self.storage_dir)
|
self.storage = FileSystemStorage(self.storage_dir)
|
||||||
name = self.storage.save("the_directory/the_file", ContentFile("data"))
|
name = self.storage.save('the_directory/subdir/the_file', ContentFile('data'))
|
||||||
dir_mode = os.stat(os.path.dirname(self.storage.path(name)))[0] & 0o777
|
file_path = Path(self.storage.path(name))
|
||||||
self.assertEqual(dir_mode, 0o777 & ~self.umask)
|
expected_mode = 0o777 & ~self.umask
|
||||||
|
self.assertEqual(file_path.parent.stat().st_mode & 0o777, expected_mode)
|
||||||
|
self.assertEqual(file_path.parent.parent.stat().st_mode & 0o777, expected_mode)
|
||||||
|
|
||||||
|
|
||||||
class FileStoragePathParsing(SimpleTestCase):
|
class FileStoragePathParsing(SimpleTestCase):
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
html {height: 100%;}
|
|
@ -4,6 +4,7 @@ import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
from pathlib import Path
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -457,12 +458,19 @@ class TestStaticFilePermissions(CollectionTestCase):
|
||||||
)
|
)
|
||||||
def test_collect_static_files_permissions(self):
|
def test_collect_static_files_permissions(self):
|
||||||
call_command('collectstatic', **self.command_params)
|
call_command('collectstatic', **self.command_params)
|
||||||
test_file = os.path.join(settings.STATIC_ROOT, "test.txt")
|
static_root = Path(settings.STATIC_ROOT)
|
||||||
test_dir = os.path.join(settings.STATIC_ROOT, "subdir")
|
test_file = static_root / 'test.txt'
|
||||||
file_mode = os.stat(test_file)[0] & 0o777
|
file_mode = test_file.stat().st_mode & 0o777
|
||||||
dir_mode = os.stat(test_dir)[0] & 0o777
|
|
||||||
self.assertEqual(file_mode, 0o655)
|
self.assertEqual(file_mode, 0o655)
|
||||||
self.assertEqual(dir_mode, 0o765)
|
tests = [
|
||||||
|
static_root / 'subdir',
|
||||||
|
static_root / 'nested',
|
||||||
|
static_root / 'nested' / 'css',
|
||||||
|
]
|
||||||
|
for directory in tests:
|
||||||
|
with self.subTest(directory=directory):
|
||||||
|
dir_mode = directory.stat().st_mode & 0o777
|
||||||
|
self.assertEqual(dir_mode, 0o765)
|
||||||
|
|
||||||
@override_settings(
|
@override_settings(
|
||||||
FILE_UPLOAD_PERMISSIONS=None,
|
FILE_UPLOAD_PERMISSIONS=None,
|
||||||
|
@ -470,12 +478,19 @@ class TestStaticFilePermissions(CollectionTestCase):
|
||||||
)
|
)
|
||||||
def test_collect_static_files_default_permissions(self):
|
def test_collect_static_files_default_permissions(self):
|
||||||
call_command('collectstatic', **self.command_params)
|
call_command('collectstatic', **self.command_params)
|
||||||
test_file = os.path.join(settings.STATIC_ROOT, "test.txt")
|
static_root = Path(settings.STATIC_ROOT)
|
||||||
test_dir = os.path.join(settings.STATIC_ROOT, "subdir")
|
test_file = static_root / 'test.txt'
|
||||||
file_mode = os.stat(test_file)[0] & 0o777
|
file_mode = test_file.stat().st_mode & 0o777
|
||||||
dir_mode = os.stat(test_dir)[0] & 0o777
|
|
||||||
self.assertEqual(file_mode, 0o666 & ~self.umask)
|
self.assertEqual(file_mode, 0o666 & ~self.umask)
|
||||||
self.assertEqual(dir_mode, 0o777 & ~self.umask)
|
tests = [
|
||||||
|
static_root / 'subdir',
|
||||||
|
static_root / 'nested',
|
||||||
|
static_root / 'nested' / 'css',
|
||||||
|
]
|
||||||
|
for directory in tests:
|
||||||
|
with self.subTest(directory=directory):
|
||||||
|
dir_mode = directory.stat().st_mode & 0o777
|
||||||
|
self.assertEqual(dir_mode, 0o777 & ~self.umask)
|
||||||
|
|
||||||
@override_settings(
|
@override_settings(
|
||||||
FILE_UPLOAD_PERMISSIONS=0o655,
|
FILE_UPLOAD_PERMISSIONS=0o655,
|
||||||
|
@ -484,12 +499,19 @@ class TestStaticFilePermissions(CollectionTestCase):
|
||||||
)
|
)
|
||||||
def test_collect_static_files_subclass_of_static_storage(self):
|
def test_collect_static_files_subclass_of_static_storage(self):
|
||||||
call_command('collectstatic', **self.command_params)
|
call_command('collectstatic', **self.command_params)
|
||||||
test_file = os.path.join(settings.STATIC_ROOT, "test.txt")
|
static_root = Path(settings.STATIC_ROOT)
|
||||||
test_dir = os.path.join(settings.STATIC_ROOT, "subdir")
|
test_file = static_root / 'test.txt'
|
||||||
file_mode = os.stat(test_file)[0] & 0o777
|
file_mode = test_file.stat().st_mode & 0o777
|
||||||
dir_mode = os.stat(test_dir)[0] & 0o777
|
|
||||||
self.assertEqual(file_mode, 0o640)
|
self.assertEqual(file_mode, 0o640)
|
||||||
self.assertEqual(dir_mode, 0o740)
|
tests = [
|
||||||
|
static_root / 'subdir',
|
||||||
|
static_root / 'nested',
|
||||||
|
static_root / 'nested' / 'css',
|
||||||
|
]
|
||||||
|
for directory in tests:
|
||||||
|
with self.subTest(directory=directory):
|
||||||
|
dir_mode = directory.stat().st_mode & 0o777
|
||||||
|
self.assertEqual(dir_mode, 0o740)
|
||||||
|
|
||||||
|
|
||||||
@override_settings(
|
@override_settings(
|
||||||
|
|
Loading…
Reference in New Issue