Skip to content

Commit 5db357a

Browse files
authored
Apply additional validation in overwrite path (#1486)
1 parent 394e5d6 commit 5db357a

File tree

2 files changed

+18
-3
lines changed

2 files changed

+18
-3
lines changed

storages/utils.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from django.core.exceptions import ImproperlyConfigured
77
from django.core.exceptions import SuspiciousFileOperation
88
from django.core.files.utils import FileProxyMixin
9+
from django.core.files.utils import validate_file_name
910
from django.utils.encoding import force_bytes
1011

1112

@@ -121,11 +122,18 @@ def lookup_env(names):
121122

122123

123124
def get_available_overwrite_name(name, max_length):
125+
# This is adapted from Django, and will be removed once
126+
# Django 5.1 is the lowest supported version
127+
dir_name, file_name = os.path.split(name)
128+
if ".." in pathlib.PurePath(dir_name).parts:
129+
raise SuspiciousFileOperation(
130+
"Detected path traversal attempt in '%s'" % dir_name
131+
)
132+
validate_file_name(file_name, allow_relative_path=True)
133+
124134
if max_length is None or len(name) <= max_length:
125135
return name
126136

127-
# Adapted from Django
128-
dir_name, file_name = os.path.split(name)
129137
file_root, file_ext = os.path.splitext(file_name)
130138
truncation = len(name) - max_length
131139

@@ -136,7 +144,9 @@ def get_available_overwrite_name(name, max_length):
136144
"Please make sure that the corresponding file field "
137145
'allows sufficient "max_length".' % name
138146
)
139-
return os.path.join(dir_name, "{}{}".format(file_root, file_ext))
147+
name = os.path.join(dir_name, "{}{}".format(file_root, file_ext))
148+
validate_file_name(name, allow_relative_path=True)
149+
return name
140150

141151

142152
def is_seekable(file_object):

tests/test_utils.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,11 @@ def test_truncates_away_filename_raises(self):
140140
with self.assertRaises(SuspiciousFileOperation):
141141
gaon(name, len(name) - 5)
142142

143+
def test_suspicious_file(self):
144+
name = "superlong/file/with/../path.txt"
145+
with self.assertRaises(SuspiciousFileOperation):
146+
gaon(name, 50)
147+
143148

144149
class TestReadBytesWrapper(TestCase):
145150
def test_with_bytes_file(self):

0 commit comments

Comments
 (0)