Skip to content
Projeler
Gruplar
Parçacıklar
Yardım
Yükleniyor...
Oturum aç / Kaydol
Gezinmeyi değiştir
D
django
Proje
Proje
Ayrıntılar
Etkinlik
Cycle Analytics
Depo (repository)
Depo (repository)
Dosyalar
Kayıtlar (commit)
Dallar (branch)
Etiketler
Katkıda bulunanlar
Grafik
Karşılaştır
Grafikler
Konular (issue)
0
Konular (issue)
0
Liste
Pano
Etiketler
Kilometre Taşları
Birleştirme (merge) Talepleri
0
Birleştirme (merge) Talepleri
0
CI / CD
CI / CD
İş akışları (pipeline)
İşler
Zamanlamalar
Grafikler
Paketler
Paketler
Wiki
Wiki
Parçacıklar
Parçacıklar
Üyeler
Üyeler
Collapse sidebar
Close sidebar
Etkinlik
Grafik
Grafikler
Yeni bir konu (issue) oluştur
İşler
Kayıtlar (commit)
Konu (issue) Panoları
Kenar çubuğunu aç
Batuhan Osman TASKAYA
django
Commits
0d8d30b7
Kaydet (Commit)
0d8d30b7
authored
Agu 08, 2014
tarafından
Tim Graham
Dosyalara gözat
Seçenekler
Dosyalara Gözat
İndir
Eposta Yamaları
Sade Fark
Fixed #23157 -- Removed O(n) algorithm when uploading duplicate file names.
This is a security fix. Disclosure following shortly.
üst
28e76581
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
122 additions
and
27 deletions
+122
-27
storage.py
django/core/files/storage.py
+5
-6
custom-file-storage.txt
docs/howto/custom-file-storage.txt
+11
-2
storage.txt
docs/ref/files/storage.txt
+12
-0
1.4.14.txt
docs/releases/1.4.14.txt
+20
-0
1.5.9.txt
docs/releases/1.5.9.txt
+20
-0
1.6.6.txt
docs/releases/1.6.6.txt
+20
-0
1.7.txt
docs/releases/1.7.txt
+7
-0
tests.py
tests/file_storage/tests.py
+27
-19
No files found.
django/core/files/storage.py
Dosyayı görüntüle @
0d8d30b7
import
os
import
os
import
errno
import
errno
import
itertools
from
datetime
import
datetime
from
datetime
import
datetime
from
django.conf
import
settings
from
django.conf
import
settings
from
django.core.exceptions
import
SuspiciousFileOperation
from
django.core.exceptions
import
SuspiciousFileOperation
from
django.core.files
import
locks
,
File
from
django.core.files
import
locks
,
File
from
django.core.files.move
import
file_move_safe
from
django.core.files.move
import
file_move_safe
from
django.utils.crypto
import
get_random_string
from
django.utils.encoding
import
force_text
,
filepath_to_uri
from
django.utils.encoding
import
force_text
,
filepath_to_uri
from
django.utils.functional
import
LazyObject
from
django.utils.functional
import
LazyObject
from
django.utils.module_loading
import
import_string
from
django.utils.module_loading
import
import_string
...
@@ -69,13 +69,12 @@ class Storage(object):
...
@@ -69,13 +69,12 @@ class Storage(object):
"""
"""
dir_name
,
file_name
=
os
.
path
.
split
(
name
)
dir_name
,
file_name
=
os
.
path
.
split
(
name
)
file_root
,
file_ext
=
os
.
path
.
splitext
(
file_name
)
file_root
,
file_ext
=
os
.
path
.
splitext
(
file_name
)
# If the filename already exists, add an underscore and a number (before
# If the filename already exists, add an underscore and a random 7
# the file extension, if one exists) to the filename until the generated
# character alphanumeric string (before the file extension, if one
# filename doesn't exist.
# exists) to the filename until the generated filename doesn't exist.
count
=
itertools
.
count
(
1
)
while
self
.
exists
(
name
):
while
self
.
exists
(
name
):
# file_ext includes the dot.
# file_ext includes the dot.
name
=
os
.
path
.
join
(
dir_name
,
"
%
s_
%
s
%
s"
%
(
file_root
,
next
(
count
),
file_ext
))
name
=
os
.
path
.
join
(
dir_name
,
"
%
s_
%
s
%
s"
%
(
file_root
,
get_random_string
(
7
),
file_ext
))
return
name
return
name
...
...
docs/howto/custom-file-storage.txt
Dosyayı görüntüle @
0d8d30b7
...
@@ -90,5 +90,14 @@ the provided filename into account. The ``name`` argument passed to this method
...
@@ -90,5 +90,14 @@ the provided filename into account. The ``name`` argument passed to this method
will have already cleaned to a filename valid for the storage system, according
will have already cleaned to a filename valid for the storage system, according
to the ``get_valid_name()`` method described above.
to the ``get_valid_name()`` method described above.
The code provided on ``Storage`` simply appends ``"_1"``, ``"_2"``, etc. to the
.. versionchanged:: 1.7
filename until it finds one that's available in the destination directory.
If a file with ``name`` already exists, an underscore plus a random 7
character alphanumeric string is appended to the filename before the
extension.
Previously, an underscore followed by a number (e.g. ``"_1"``, ``"_2"``,
etc.) was appended to the filename until an avaible name in the destination
directory was found. A malicious user could exploit this deterministic
algorithm to create a denial-of-service attack. This change was also made
in Django 1.6.6, 1.5.9, and 1.4.14.
docs/ref/files/storage.txt
Dosyayı görüntüle @
0d8d30b7
...
@@ -112,6 +112,18 @@ The Storage Class
...
@@ -112,6 +112,18 @@ The Storage Class
available for new content to be written to on the target storage
available for new content to be written to on the target storage
system.
system.
.. versionchanged:: 1.7
If a file with ``name`` already exists, an underscore plus a random 7
character alphanumeric string is appended to the filename before the
extension.
Previously, an underscore followed by a number (e.g. ``"_1"``, ``"_2"``,
etc.) was appended to the filename until an avaible name in the
destination directory was found. A malicious user could exploit this
deterministic algorithm to create a denial-of-service attack. This
change was also made in Django 1.6.6, 1.5.9, and 1.4.14.
.. method:: get_valid_name(name)
.. method:: get_valid_name(name)
Returns a filename based on the ``name`` parameter that's suitable
Returns a filename based on the ``name`` parameter that's suitable
...
...
docs/releases/1.4.14.txt
Dosyayı görüntüle @
0d8d30b7
...
@@ -18,3 +18,23 @@ To remedy this, URL reversing now ensures that no URL starts with two slashes
...
@@ -18,3 +18,23 @@ To remedy this, URL reversing now ensures that no URL starts with two slashes
(//), replacing the second slash with its URL encoded counterpart (%2F). This
(//), replacing the second slash with its URL encoded counterpart (%2F). This
approach ensures that semantics stay the same, while making the URL relative to
approach ensures that semantics stay the same, while making the URL relative to
the domain and not to the scheme.
the domain and not to the scheme.
File upload denial-of-service
=============================
Before this release, Django's file upload handing in its default configuration
may degrade to producing a huge number of ``os.stat()`` system calls when a
duplicate filename is uploaded. Since ``stat()`` may invoke IO, this may produce
a huge data-dependent slowdown that slowly worsens over time. The net result is
that given enough time, a user with the ability to upload files can cause poor
performance in the upload handler, eventually causing it to become very slow
simply by uploading 0-byte files. At this point, even a slow network connection
and few HTTP requests would be all that is necessary to make a site unavailable.
We've remedied the issue by changing the algorithm for generating file names
if a file with the uploaded name already exists.
:meth:`Storage.get_available_name()
<django.core.files.storage.Storage.get_available_name>` now appends an
underscore plus a random 7 character alphanumeric string (e.g. ``"_x3a1gho"``),
rather than iterating through an underscore followed by a number (e.g. ``"_1"``,
``"_2"``, etc.).
docs/releases/1.5.9.txt
Dosyayı görüntüle @
0d8d30b7
...
@@ -18,3 +18,23 @@ To remedy this, URL reversing now ensures that no URL starts with two slashes
...
@@ -18,3 +18,23 @@ To remedy this, URL reversing now ensures that no URL starts with two slashes
(//), replacing the second slash with its URL encoded counterpart (%2F). This
(//), replacing the second slash with its URL encoded counterpart (%2F). This
approach ensures that semantics stay the same, while making the URL relative to
approach ensures that semantics stay the same, while making the URL relative to
the domain and not to the scheme.
the domain and not to the scheme.
File upload denial-of-service
=============================
Before this release, Django's file upload handing in its default configuration
may degrade to producing a huge number of ``os.stat()`` system calls when a
duplicate filename is uploaded. Since ``stat()`` may invoke IO, this may produce
a huge data-dependent slowdown that slowly worsens over time. The net result is
that given enough time, a user with the ability to upload files can cause poor
performance in the upload handler, eventually causing it to become very slow
simply by uploading 0-byte files. At this point, even a slow network connection
and few HTTP requests would be all that is necessary to make a site unavailable.
We've remedied the issue by changing the algorithm for generating file names
if a file with the uploaded name already exists.
:meth:`Storage.get_available_name()
<django.core.files.storage.Storage.get_available_name>` now appends an
underscore plus a random 7 character alphanumeric string (e.g. ``"_x3a1gho"``),
rather than iterating through an underscore followed by a number (e.g. ``"_1"``,
``"_2"``, etc.).
docs/releases/1.6.6.txt
Dosyayı görüntüle @
0d8d30b7
...
@@ -19,6 +19,26 @@ To remedy this, URL reversing now ensures that no URL starts with two slashes
...
@@ -19,6 +19,26 @@ To remedy this, URL reversing now ensures that no URL starts with two slashes
approach ensures that semantics stay the same, while making the URL relative to
approach ensures that semantics stay the same, while making the URL relative to
the domain and not to the scheme.
the domain and not to the scheme.
File upload denial-of-service
=============================
Before this release, Django's file upload handing in its default configuration
may degrade to producing a huge number of ``os.stat()`` system calls when a
duplicate filename is uploaded. Since ``stat()`` may invoke IO, this may produce
a huge data-dependent slowdown that slowly worsens over time. The net result is
that given enough time, a user with the ability to upload files can cause poor
performance in the upload handler, eventually causing it to become very slow
simply by uploading 0-byte files. At this point, even a slow network connection
and few HTTP requests would be all that is necessary to make a site unavailable.
We've remedied the issue by changing the algorithm for generating file names
if a file with the uploaded name already exists.
:meth:`Storage.get_available_name()
<django.core.files.storage.Storage.get_available_name>` now appends an
underscore plus a random 7 character alphanumeric string (e.g. ``"_x3a1gho"``),
rather than iterating through an underscore followed by a number (e.g. ``"_1"``,
``"_2"``, etc.).
Bugfixes
Bugfixes
========
========
...
...
docs/releases/1.7.txt
Dosyayı görüntüle @
0d8d30b7
...
@@ -588,6 +588,13 @@ File Uploads
...
@@ -588,6 +588,13 @@ File Uploads
the client. Partially uploaded files are also closed as long as they are
the client. Partially uploaded files are also closed as long as they are
named ``file`` in the upload handler.
named ``file`` in the upload handler.
* :meth:`Storage.get_available_name()
<django.core.files.storage.Storage.get_available_name>` now appends an
underscore plus a random 7 character alphanumeric string (e.g.
``"_x3a1gho"``), rather than iterating through an underscore followed by a
number (e.g. ``"_1"``, ``"_2"``, etc.) to prevent a denial-of-service attack.
This change was also made in the 1.6.6, 1.5.9, and 1.4.14 security releases.
Forms
Forms
^^^^^
^^^^^
...
...
tests/file_storage/tests.py
Dosyayı görüntüle @
0d8d30b7
...
@@ -30,6 +30,9 @@ from django.utils._os import upath
...
@@ -30,6 +30,9 @@ from django.utils._os import upath
from
.models
import
Storage
,
temp_storage
,
temp_storage_location
from
.models
import
Storage
,
temp_storage
,
temp_storage_location
FILE_SUFFIX_REGEX
=
'[A-Za-z0-9]{7}'
class
GetStorageClassTests
(
SimpleTestCase
):
class
GetStorageClassTests
(
SimpleTestCase
):
def
test_get_filesystem_storage
(
self
):
def
test_get_filesystem_storage
(
self
):
...
@@ -465,14 +468,16 @@ class FileFieldStorageTests(unittest.TestCase):
...
@@ -465,14 +468,16 @@ class FileFieldStorageTests(unittest.TestCase):
# Save another file with the same name.
# Save another file with the same name.
obj2
=
Storage
()
obj2
=
Storage
()
obj2
.
normal
.
save
(
"django_test.txt"
,
ContentFile
(
"more content"
))
obj2
.
normal
.
save
(
"django_test.txt"
,
ContentFile
(
"more content"
))
self
.
assertEqual
(
obj2
.
normal
.
name
,
"tests/django_test_1.txt"
)
obj2_name
=
obj2
.
normal
.
name
six
.
assertRegex
(
self
,
obj2_name
,
"tests/django_test_
%
s.txt"
%
FILE_SUFFIX_REGEX
)
self
.
assertEqual
(
obj2
.
normal
.
size
,
12
)
self
.
assertEqual
(
obj2
.
normal
.
size
,
12
)
obj2
.
normal
.
close
()
obj2
.
normal
.
close
()
# Deleting an object does not delete the file it uses.
# Deleting an object does not delete the file it uses.
obj2
.
delete
()
obj2
.
delete
()
obj2
.
normal
.
save
(
"django_test.txt"
,
ContentFile
(
"more content"
))
obj2
.
normal
.
save
(
"django_test.txt"
,
ContentFile
(
"more content"
))
self
.
assertEqual
(
obj2
.
normal
.
name
,
"tests/django_test_2.txt"
)
self
.
assertNotEqual
(
obj2_name
,
obj2
.
normal
.
name
)
six
.
assertRegex
(
self
,
obj2
.
normal
.
name
,
"tests/django_test_
%
s.txt"
%
FILE_SUFFIX_REGEX
)
obj2
.
normal
.
close
()
obj2
.
normal
.
close
()
def
test_filefield_read
(
self
):
def
test_filefield_read
(
self
):
...
@@ -485,17 +490,18 @@ class FileFieldStorageTests(unittest.TestCase):
...
@@ -485,17 +490,18 @@ class FileFieldStorageTests(unittest.TestCase):
self
.
assertEqual
(
list
(
obj
.
normal
.
chunks
(
chunk_size
=
2
)),
[
b
"co"
,
b
"nt"
,
b
"en"
,
b
"t"
])
self
.
assertEqual
(
list
(
obj
.
normal
.
chunks
(
chunk_size
=
2
)),
[
b
"co"
,
b
"nt"
,
b
"en"
,
b
"t"
])
obj
.
normal
.
close
()
obj
.
normal
.
close
()
def
test_
file_numbering
(
self
):
def
test_
duplicate_filename
(
self
):
# Multiple files with the same name get _
N
appended to them.
# Multiple files with the same name get _
(7 random chars)
appended to them.
objs
=
[
Storage
()
for
i
in
range
(
3
)]
objs
=
[
Storage
()
for
i
in
range
(
2
)]
for
o
in
objs
:
for
o
in
objs
:
o
.
normal
.
save
(
"multiple_files.txt"
,
ContentFile
(
"Same Content"
))
o
.
normal
.
save
(
"multiple_files.txt"
,
ContentFile
(
"Same Content"
))
self
.
assertEqual
(
try
:
[
o
.
normal
.
name
for
o
in
objs
],
names
=
[
o
.
normal
.
name
for
o
in
objs
]
[
"tests/multiple_files.txt"
,
"tests/multiple_files_1.txt"
,
"tests/multiple_files_2.txt"
]
self
.
assertEqual
(
names
[
0
],
"tests/multiple_files.txt"
)
)
six
.
assertRegex
(
self
,
names
[
1
],
"tests/multiple_files_
%
s.txt"
%
FILE_SUFFIX_REGEX
)
for
o
in
objs
:
finally
:
o
.
delete
()
for
o
in
objs
:
o
.
delete
()
def
test_filefield_default
(
self
):
def
test_filefield_default
(
self
):
# Default values allow an object to access a single file.
# Default values allow an object to access a single file.
...
@@ -587,10 +593,9 @@ class FileSaveRaceConditionTest(unittest.TestCase):
...
@@ -587,10 +593,9 @@ class FileSaveRaceConditionTest(unittest.TestCase):
self
.
thread
.
start
()
self
.
thread
.
start
()
self
.
save_file
(
'conflict'
)
self
.
save_file
(
'conflict'
)
self
.
thread
.
join
()
self
.
thread
.
join
()
self
.
assertTrue
(
self
.
storage
.
exists
(
'conflict'
))
files
=
sorted
(
os
.
listdir
(
self
.
storage_dir
))
self
.
assertTrue
(
self
.
storage
.
exists
(
'conflict_1'
))
self
.
assertEqual
(
files
[
0
],
'conflict'
)
self
.
storage
.
delete
(
'conflict'
)
six
.
assertRegex
(
self
,
files
[
1
],
'conflict_
%
s'
%
FILE_SUFFIX_REGEX
)
self
.
storage
.
delete
(
'conflict_1'
)
@unittest.skipIf
(
sys
.
platform
.
startswith
(
'win'
),
"Windows only partially supports umasks and chmod."
)
@unittest.skipIf
(
sys
.
platform
.
startswith
(
'win'
),
"Windows only partially supports umasks and chmod."
)
...
@@ -651,9 +656,10 @@ class FileStoragePathParsing(unittest.TestCase):
...
@@ -651,9 +656,10 @@ class FileStoragePathParsing(unittest.TestCase):
self
.
storage
.
save
(
'dotted.path/test'
,
ContentFile
(
"1"
))
self
.
storage
.
save
(
'dotted.path/test'
,
ContentFile
(
"1"
))
self
.
storage
.
save
(
'dotted.path/test'
,
ContentFile
(
"2"
))
self
.
storage
.
save
(
'dotted.path/test'
,
ContentFile
(
"2"
))
files
=
sorted
(
os
.
listdir
(
os
.
path
.
join
(
self
.
storage_dir
,
'dotted.path'
)))
self
.
assertFalse
(
os
.
path
.
exists
(
os
.
path
.
join
(
self
.
storage_dir
,
'dotted_.path'
)))
self
.
assertFalse
(
os
.
path
.
exists
(
os
.
path
.
join
(
self
.
storage_dir
,
'dotted_.path'
)))
self
.
assert
True
(
os
.
path
.
exists
(
os
.
path
.
join
(
self
.
storage_dir
,
'dotted.path/test'
))
)
self
.
assert
Equal
(
files
[
0
],
'test'
)
s
elf
.
assertTrue
(
os
.
path
.
exists
(
os
.
path
.
join
(
self
.
storage_dir
,
'dotted.path/test_1'
))
)
s
ix
.
assertRegex
(
self
,
files
[
1
],
'test_
%
s'
%
FILE_SUFFIX_REGEX
)
def
test_first_character_dot
(
self
):
def
test_first_character_dot
(
self
):
"""
"""
...
@@ -663,8 +669,10 @@ class FileStoragePathParsing(unittest.TestCase):
...
@@ -663,8 +669,10 @@ class FileStoragePathParsing(unittest.TestCase):
self
.
storage
.
save
(
'dotted.path/.test'
,
ContentFile
(
"1"
))
self
.
storage
.
save
(
'dotted.path/.test'
,
ContentFile
(
"1"
))
self
.
storage
.
save
(
'dotted.path/.test'
,
ContentFile
(
"2"
))
self
.
storage
.
save
(
'dotted.path/.test'
,
ContentFile
(
"2"
))
self
.
assertTrue
(
os
.
path
.
exists
(
os
.
path
.
join
(
self
.
storage_dir
,
'dotted.path/.test'
)))
files
=
sorted
(
os
.
listdir
(
os
.
path
.
join
(
self
.
storage_dir
,
'dotted.path'
)))
self
.
assertTrue
(
os
.
path
.
exists
(
os
.
path
.
join
(
self
.
storage_dir
,
'dotted.path/.test_1'
)))
self
.
assertFalse
(
os
.
path
.
exists
(
os
.
path
.
join
(
self
.
storage_dir
,
'dotted_.path'
)))
self
.
assertEqual
(
files
[
0
],
'.test'
)
six
.
assertRegex
(
self
,
files
[
1
],
'.test_
%
s'
%
FILE_SUFFIX_REGEX
)
class
ContentFileStorageTestCase
(
unittest
.
TestCase
):
class
ContentFileStorageTestCase
(
unittest
.
TestCase
):
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment