Login
[x]
Log in using an account from:
Fedora Account System
Red Hat Associate
Red Hat Customer
Or login using a Red Hat Bugzilla account
Forgot Password
Login:
Hide Forgot
Create an Account
Red Hat Bugzilla – Attachment 926638 Details for
Bug 1129952
CVE-2014-0481 Django: file upload denial of service
[?]
New
Simple Search
Advanced Search
My Links
Browse
Requests
Reports
Current State
Search
Tabular reports
Graphical reports
Duplicates
Other Reports
User Changes
Plotly Reports
Bug Status
Bug Severity
Non-Defaults
|
Product Dashboard
Help
Page Help!
Bug Writing Guidelines
What's new
Browser Support Policy
5.0.4.rh83 Release notes
FAQ
Guides index
User guide
Web Services
Contact
Legal
This site requires JavaScript to be enabled to function correctly, please enable it.
[patch]
1.4 patch
file-upload-1.4.diff (text/plain), 11.62 KB, created by
Murray McAllister
on 2014-08-14 04:45:58 UTC
(
hide
)
Description:
1.4 patch
Filename:
MIME Type:
Creator:
Murray McAllister
Created:
2014-08-14 04:45:58 UTC
Size:
11.62 KB
patch
obsolete
>commit 30042d475bf084c6723c6217a21598d9247a9c41 >Author: Tim Graham <timograham@gmail.com> >Date: Fri Aug 8 10:20:08 2014 -0400 > > [1.4.x] Fixed #23157 -- Removed O(n) algorithm when uploading duplicate file names. > > This is a security fix. Disclosure following shortly. > >diff --git a/django/core/files/storage.py b/django/core/files/storage.py >index aa62175..3040764 100644 >--- a/django/core/files/storage.py >+++ b/django/core/files/storage.py >@@ -1,13 +1,13 @@ > import os > import errno > import urlparse >-import itertools > from datetime import datetime > > from django.conf import settings > from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation > from django.core.files import locks, File > from django.core.files.move import file_move_safe >+from django.utils.crypto import get_random_string > from django.utils.encoding import force_unicode, filepath_to_uri > from django.utils.functional import LazyObject > from django.utils.importlib import import_module >@@ -63,13 +63,12 @@ class Storage(object): > """ > dir_name, file_name = os.path.split(name) > file_root, file_ext = os.path.splitext(file_name) >- # If the filename already exists, add an underscore and a number (before >- # the file extension, if one exists) to the filename until the generated >- # filename doesn't exist. >- count = itertools.count(1) >+ # If the filename already exists, add an underscore and a random 7 >+ # character alphanumeric string (before the file extension, if one >+ # exists) to the filename until the generated filename doesn't exist. > while self.exists(name): > # file_ext includes the dot. >- name = os.path.join(dir_name, "%s_%s%s" % (file_root, count.next(), file_ext)) >+ name = os.path.join(dir_name, "%s_%s%s" % (file_root, get_random_string(7), file_ext)) > > return name > >diff --git a/docs/howto/custom-file-storage.txt b/docs/howto/custom-file-storage.txt >index 5f1dae1..ffcbe4a 100644 >--- a/docs/howto/custom-file-storage.txt >+++ b/docs/howto/custom-file-storage.txt >@@ -86,5 +86,13 @@ 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 > to the ``get_valid_name()`` method described above. > >-The code provided on ``Storage`` simply appends ``"_1"``, ``"_2"``, etc. to the >-filename until it finds one that's available in the destination directory. >+.. versionchanged:: 1.4.14 >+ >+ 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. >diff --git a/docs/ref/files/storage.txt b/docs/ref/files/storage.txt >index b3f8909..252e8e4 100644 >--- a/docs/ref/files/storage.txt >+++ b/docs/ref/files/storage.txt >@@ -18,7 +18,7 @@ Django provides two convenient ways to access the current storage class: > .. function:: get_storage_class([import_path=None]) > > Returns a class or module which implements the storage API. >- >+ > When called without the ``import_path`` parameter ``get_storage_class`` > will return the current default storage system as defined by > :setting:`DEFAULT_FILE_STORAGE`. If ``import_path`` is provided, >@@ -35,9 +35,9 @@ The FileSystemStorage Class > basic file storage on a local filesystem. It inherits from > :class:`~django.core.files.storage.Storage` and provides implementations > for all the public methods thereof. >- >+ > .. note:: >- >+ > The :class:`FileSystemStorage.delete` method will not raise > raise an exception if the given file name does not exist. > >@@ -85,6 +85,16 @@ The Storage Class > available for new content to be written to on the target storage > system. > >+ .. versionchanged:: 1.4.14 >+ >+ 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. > > .. method:: get_valid_name(name) > >diff --git a/docs/releases/1.4.14.txt b/docs/releases/1.4.14.txt >index 28390c9..6c140ee 100644 >--- a/docs/releases/1.4.14.txt >+++ b/docs/releases/1.4.14.txt >@@ -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 > approach ensures that semantics stay the same, while making the URL relative to > 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.). >diff --git a/tests/modeltests/files/tests.py b/tests/modeltests/files/tests.py >index 50017be..3864d27 100644 >--- a/tests/modeltests/files/tests.py >+++ b/tests/modeltests/files/tests.py >@@ -8,10 +8,14 @@ from django.core.files import File > from django.core.files.base import ContentFile > from django.core.files.uploadedfile import SimpleUploadedFile > from django.test import TestCase >+from django.utils import six > > from .models import Storage, temp_storage, temp_storage_location > > >+FILE_SUFFIX_REGEX = '[A-Za-z0-9]{7}' >+ >+ > class FileTests(TestCase): > def tearDown(self): > shutil.rmtree(temp_storage_location) >@@ -57,27 +61,28 @@ class FileTests(TestCase): > # Save another file with the same name. > obj2 = Storage() > 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) > > # Push the objects into the cache to make sure they pickle properly > cache.set("obj1", obj1) > cache.set("obj2", obj2) >- self.assertEqual(cache.get("obj2").normal.name, "tests/django_test_1.txt") >+ six.assertRegex(self, cache.get("obj2").normal.name, "tests/django_test_%s.txt" % FILE_SUFFIX_REGEX) > > # Deleting an object does not delete the file it uses. > obj2.delete() > 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) > > # Multiple files with the same name get _N appended to them. >- objs = [Storage() for i in range(3)] >+ objs = [Storage() for i in range(2)] > for o in objs: > o.normal.save("multiple_files.txt", ContentFile("Same Content")) >- self.assertEqual( >- [o.normal.name for o in objs], >- ["tests/multiple_files.txt", "tests/multiple_files_1.txt", "tests/multiple_files_2.txt"] >- ) >+ names = [o.normal.name for o in objs] >+ 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: > o.delete() > >diff --git a/tests/regressiontests/file_storage/tests.py b/tests/regressiontests/file_storage/tests.py >index 769e2c7..7c8027a 100644 >--- a/tests/regressiontests/file_storage/tests.py >+++ b/tests/regressiontests/file_storage/tests.py >@@ -23,7 +23,7 @@ from django.core.files.images import get_image_dimensions > from django.core.files.storage import FileSystemStorage, get_storage_class > from django.core.files.uploadedfile import UploadedFile > from django.test import SimpleTestCase >-from django.utils import unittest >+from django.utils import six, unittest > > # Try to import PIL in either of the two ways it can end up installed. > # Checking for the existence of Image is enough for CPython, but >@@ -37,6 +37,9 @@ except ImportError: > Image = None > > >+FILE_SUFFIX_REGEX = '[A-Za-z0-9]{7}' >+ >+ > class GetStorageClassTests(SimpleTestCase): > > def test_get_filesystem_storage(self): >@@ -417,10 +420,9 @@ class FileSaveRaceConditionTest(unittest.TestCase): > self.thread.start() > name = self.save_file('conflict') > self.thread.join() >- self.assertTrue(self.storage.exists('conflict')) >- self.assertTrue(self.storage.exists('conflict_1')) >- self.storage.delete('conflict') >- self.storage.delete('conflict_1') >+ files = sorted(os.listdir(self.storage_dir)) >+ self.assertEqual(files[0], 'conflict') >+ six.assertRegex(self, files[1], 'conflict_%s' % FILE_SUFFIX_REGEX) > > class FileStoragePermissions(unittest.TestCase): > def setUp(self): >@@ -457,9 +459,10 @@ class FileStoragePathParsing(unittest.TestCase): > self.storage.save('dotted.path/test', ContentFile("1")) > 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.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test'))) >- self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test_1'))) >+ self.assertEqual(files[0], 'test') >+ six.assertRegex(self, files[1], 'test_%s' % FILE_SUFFIX_REGEX) > > def test_first_character_dot(self): > """ >@@ -472,10 +475,12 @@ class FileStoragePathParsing(unittest.TestCase): > self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test'))) > # Before 2.6, a leading dot was treated as an extension, and so > # underscore gets added to beginning instead of end. >+ files = sorted(os.listdir(os.path.join(self.storage_dir, 'dotted.path'))) >+ self.assertEqual(files[0], '.test') > if sys.version_info < (2, 6): >- self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/_1.test'))) >+ six.assertRegex(self, files[1], '_%s.test' % FILE_SUFFIX_REGEX) > else: >- self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test_1'))) >+ six.assertRegex(self, files[1], '.test_%s' % FILE_SUFFIX_REGEX) > > class DimensionClosingBug(unittest.TestCase): > """
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 1129952
: 926638 |
926639
|
926640