[Python-modules-commits] [python-django] 01/01: LTS security upload: https://www.djangoproject.com/weblog/2014/aug/20/security/

Raphaël Hertzog hertzog at moszumanska.debian.org
Mon Sep 29 08:21:51 UTC 2014


This is an automated email from the git hooks/post-receive script.

hertzog pushed a commit to branch debian/squeeze
in repository python-django.

commit 22968976e824d8544c116fe9468a3f181a18f93a
Author: Raphaël Hertzog <hertzog at debian.org>
Date:   Mon Sep 29 07:56:22 2014 +0000

    LTS security upload: https://www.djangoproject.com/weblog/2014/aug/20/security/
    
    * LTS security upload:
      https://www.djangoproject.com/weblog/2014/aug/20/security/
      - CVE-2014-0480.patch: reverse() generating URLs pointing
        to other hosts
      - CVE-2014-0481.patch: file upload denial of service
      - CVE-2014-0482.patch: RemoteUserMiddleware session hijacking
      - CVE-2014-0483.patch: data leakage via querystring manipulation in admin
    * Add no-network-access-on-builder.patch to disable regression tests
      using the network.
    * Add get-random-string-backport.patch to backport get_random_string()
      needed by CVE-2014-0481.patch.
---
 debian/changelog                                  |  16 ++
 debian/patches/CVE-2014-0480.patch                |  96 ++++++++++
 debian/patches/CVE-2014-0481.patch                | 211 ++++++++++++++++++++++
 debian/patches/CVE-2014-0482.patch                |  92 ++++++++++
 debian/patches/CVE-2014-0483.patch                |  43 +++++
 debian/patches/get-random-string-backport.patch   |  53 ++++++
 debian/patches/no-network-access-on-builder.patch |  30 +++
 debian/patches/series                             |   6 +
 8 files changed, 547 insertions(+)

diff --git a/debian/changelog b/debian/changelog
index 8bcfc7d..dba59dd 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,19 @@
+python-django (1.2.3-3+squeeze11) squeeze-lts; urgency=low
+
+  * LTS security upload:
+    https://www.djangoproject.com/weblog/2014/aug/20/security/
+    - CVE-2014-0480.patch: reverse() generating URLs pointing
+      to other hosts
+    - CVE-2014-0481.patch: file upload denial of service
+    - CVE-2014-0482.patch: RemoteUserMiddleware session hijacking
+    - CVE-2014-0483.patch: data leakage via querystring manipulation in admin
+  * Add no-network-access-on-builder.patch to disable regression tests
+    using the network.
+  * Add get-random-string-backport.patch to backport get_random_string()
+    needed by CVE-2014-0481.patch.
+
+ -- Raphaël Hertzog <hertzog at debian.org>  Mon, 29 Sep 2014 07:52:17 +0000
+
 python-django (1.2.3-3+squeeze10) squeeze-security; urgency=high
 
   * Non-maintainer upload by the Security Team.
diff --git a/debian/patches/CVE-2014-0480.patch b/debian/patches/CVE-2014-0480.patch
new file mode 100644
index 0000000..4f7834d
--- /dev/null
+++ b/debian/patches/CVE-2014-0480.patch
@@ -0,0 +1,96 @@
+Description: Prevented data leakage in contrib.admin via query string manipulation
+ Patch backported by Thorsten Alteholz and Raphaël Hertzog.
+Origin: backport, https://github.com/django/django/commit/027bd348642007617518379f8b02546abacaa6e0
+Author: Simon Charette <charette.s at gmail.com>
+Last-Update: 2014-09-26
+
+--- /dev/null
++++ b/django/contrib/admin/exceptions.py
+@@ -0,0 +1,6 @@
++from django.core.exceptions import SuspiciousOperation
++
++
++class DisallowedModelAdminToField(SuspiciousOperation):
++    """Invalid to_field was passed to admin view via URL query string"""
++    pass
+--- a/django/contrib/admin/options.py
++++ b/django/contrib/admin/options.py
+@@ -306,6 +306,24 @@ class ModelAdmin(BaseModelAdmin):
+         return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
+     media = property(_media)
+ 
++    def to_field_allowed(self, request, to_field):
++        opts = self.model._meta
++
++        try:
++            field = opts.get_field(to_field)
++        except FieldDoesNotExist:
++            return False
++
++        # Make sure at least one of the models registered for this site
++        # references this field.
++        registered_models = self.admin_site._registry
++        for related_object in opts.get_all_related_objects():
++            if (related_object.model in registered_models and
++                    field == related_object.field.rel.get_related_field()):
++                return True
++
++        return False
++
+     def has_add_permission(self, request):
+         "Returns True if the given request has permission to add an object."
+         opts = self.opts
+--- a/django/contrib/admin/views/main.py
++++ b/django/contrib/admin/views/main.py
+@@ -1,4 +1,5 @@
+ from django.contrib.admin.filterspecs import FilterSpec
++from django.contrib.admin.exceptions import DisallowedModelAdminToField
+ from django.contrib.admin.options import IncorrectLookupParameters
+ from django.contrib.admin.util import quote
+ from django.core.exceptions import SuspiciousOperation
+@@ -50,7 +51,10 @@ class ChangeList(object):
+             self.page_num = 0
+         self.show_all = ALL_VAR in request.GET
+         self.is_popup = IS_POPUP_VAR in request.GET
+-        self.to_field = request.GET.get(TO_FIELD_VAR)
++        to_field = request.GET.get(TO_FIELD_VAR)
++        if to_field and not model_admin.to_field_allowed(request, to_field):
++            raise DisallowedModelAdminToField("The field %s cannot be referenced." % to_field)
++        self.to_field = to_field
+         self.params = dict(request.GET.items())
+         if PAGE_VAR in self.params:
+             del self.params[PAGE_VAR]
+--- a/tests/regressiontests/admin_views/tests.py
++++ b/tests/regressiontests/admin_views/tests.py
+@@ -11,6 +11,8 @@ from django.contrib.contenttypes.models
+ from django.contrib.admin.models import LogEntry, DELETION
+ from django.contrib.admin.sites import LOGIN_FORM_KEY
+ from django.contrib.admin.util import quote
++from django.contrib.admin.views.main import TO_FIELD_VAR
++from django.contrib.admin.exceptions import DisallowedModelAdminToField
+ from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
+ from django.forms.util import ErrorList
+ from django.test import TestCase
+@@ -303,6 +305,22 @@ class AdminViewBasicTest(TestCase):
+             self.client.get, "/test_admin/admin/admin_views/album/?owner__email__startswith=fuzzy"
+         )
+ 
++    def test_disallowed_to_field(self):
++        self.assertRaises(DisallowedModelAdminToField,
++            self.client.get, "/test_admin/admin/admin_views/section/",
++            {TO_FIELD_VAR: 'missing_field'})
++
++        # Specifying a field that is not refered by any other model registered
++        # to this admin site should raise an exception.
++        self.assertRaises(DisallowedModelAdminToField,
++            self.client.get, "/test_admin/admin/admin_views/section/",
++            {TO_FIELD_VAR: 'name'})
++
++        # Specifying a field referenced by another model should be allowed.
++        response = self.client.get("/test_admin/admin/admin_views/section/",
++                                   {TO_FIELD_VAR: 'id'})
++        self.assertEqual(response.status_code, 200)
++
+ class SaveAsTests(TestCase):
+     fixtures = ['admin-views-users.xml','admin-views-person.xml']
+ 
diff --git a/debian/patches/CVE-2014-0481.patch b/debian/patches/CVE-2014-0481.patch
new file mode 100644
index 0000000..6448a4b
--- /dev/null
+++ b/debian/patches/CVE-2014-0481.patch
@@ -0,0 +1,211 @@
+commit 30042d475bf084c6723c6217a21598d9247a9c41
+Author: Tim Graham <timograham at 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.
+
+Debian note: This patch also includes the minimum backport of additional
+functionality to django.utils.six to support this patch, but does not change
+any functionality therein.
+
+--- a/django/core/files/storage.py
++++ b/django/core/files/storage.py
+@@ -1,12 +1,12 @@
+ import os
+ import errno
+ import urlparse
+-import itertools
+ 
+ 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
+ from django.utils.functional import LazyObject
+ from django.utils.importlib import import_module
+@@ -66,13 +66,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
+ 
+--- a/docs/howto/custom-file-storage.txt
++++ b/docs/howto/custom-file-storage.txt
+@@ -86,5 +86,13 @@ the provided filename into account. The
+ 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.
+--- a/tests/regressiontests/file_storage/tests.py
++++ b/tests/regressiontests/file_storage/tests.py
+@@ -1,5 +1,6 @@
+ # -*- coding: utf-8 -*-
+ import os
++import re
+ import shutil
+ import sys
+ import tempfile
+@@ -31,15 +32,15 @@ except ImportError:
+ 
+ class FileStorageTests(unittest.TestCase):
+     storage_class = FileSystemStorage
+-    
++
+     def setUp(self):
+         self.temp_dir = tempfile.mktemp()
+         os.makedirs(self.temp_dir)
+         self.storage = self.storage_class(location=self.temp_dir)
+-    
++
+     def tearDown(self):
+         os.rmdir(self.temp_dir)
+-        
++
+     def test_file_access_options(self):
+         """
+         Standard file access options are available, and work as expected.
+@@ -53,7 +54,7 @@ class FileStorageTests(unittest.TestCase
+         f = self.storage.open('storage_test', 'r')
+         self.assertEqual(f.read(), 'storage contents')
+         f.close()
+-        
++
+         self.storage.delete('storage_test')
+         self.failIf(self.storage.exists('storage_test'))
+ 
+@@ -81,7 +82,7 @@ class CustomStorage(FileSystemStorage):
+ 
+ class CustomStorageTests(FileStorageTests):
+     storage_class = CustomStorage
+-    
++
+     def test_custom_get_available_name(self):
+         first = self.storage.save('custom_storage', ContentFile('custom contents'))
+         self.assertEqual(first, 'custom_storage')
+@@ -109,6 +110,9 @@ class SlowFile(ContentFile):
+         time.sleep(1)
+         return super(ContentFile, self).chunks()
+ 
++FILE_SUFFIX_REGEX = '[A-Za-z0-9]{7}'
++
++
+ class FileSaveRaceConditionTest(TestCase):
+     def setUp(self):
+         self.storage_dir = tempfile.mkdtemp()
+@@ -125,10 +129,9 @@ class FileSaveRaceConditionTest(TestCase
+         self.thread.start()
+         name = self.save_file('conflict')
+         self.thread.join()
+-        self.assert_(self.storage.exists('conflict'))
+-        self.assert_(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')
++        self.assertTrue(re.match('conflict_%s' % FILE_SUFFIX_REGEX, files[1]))
+ 
+ class FileStoragePermissions(TestCase):
+     def setUp(self):
+@@ -165,9 +168,11 @@ class FileStoragePathParsing(TestCase):
+         self.storage.save('dotted.path/test', ContentFile("1"))
+         self.storage.save('dotted.path/test', ContentFile("2"))
+ 
+-        self.failIf(os.path.exists(os.path.join(self.storage_dir, 'dotted_.path')))
+-        self.assert_(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test')))
+-        self.assert_(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test_1')))
++        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.assertEqual(files[0], 'test')
++        self.assertTrue(re.match('test_%s' % FILE_SUFFIX_REGEX, files[1]))
++
+ 
+     def test_first_character_dot(self):
+         """
+@@ -180,10 +185,13 @@ class FileStoragePathParsing(TestCase):
+         self.assert_(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.assert_(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/_1.test')))
++            self.assertTrue(re.match('_%s.test' % FILE_SUFFIX_REGEX, files[1]))
+         else:
+-            self.assert_(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test_1')))
++            self.assertTrue(re.match('.test_%s' % FILE_SUFFIX_REGEX, files[1]))
++
+ 
+ if Image is not None:
+     class DimensionClosingBug(TestCase):
+--- a/tests/modeltests/files/models.py
++++ b/tests/modeltests/files/models.py
+@@ -90,8 +90,8 @@ True
+ 
+ >>> obj2 = Storage()
+ >>> obj2.normal.save('django_test.txt', ContentFile('more content'))
+->>> obj2.normal
+-<FieldFile: tests/django_test_1.txt>
++>>> obj2.normal # doctest: +ELLIPSIS
++<FieldFile: tests/django_test_....txt>
+ >>> obj2.normal.size
+ 12
+ 
+@@ -100,24 +100,26 @@ True
+ >>> from django.core.cache import cache
+ >>> cache.set('obj1', obj1)
+ >>> cache.set('obj2', obj2)
+->>> cache.get('obj2').normal
+-<FieldFile: tests/django_test_1.txt>
++>>> cache.get('obj2').normal # doctest: +ELLIPSIS
++<FieldFile: tests/django_test_....txt>
+ 
+ # Deleting an object deletes the file it uses, if there are no other objects
+ # still using that file.
+ 
+ >>> obj2.delete()
+ >>> obj2.normal.save('django_test.txt', ContentFile('more content'))
+->>> obj2.normal
+-<FieldFile: tests/django_test_1.txt>
++>>> obj2.normal # doctest: +ELLIPSIS
++<FieldFile: tests/django_test_....txt>
+ 
+ # Multiple files with the same name get _N appended to them.
+ 
+ >>> objs = [Storage() for i in range(3)]
+ >>> for o in objs:
+ ...     o.normal.save('multiple_files.txt', ContentFile('Same Content'))
+->>> [o.normal for o in objs]
+-[<FieldFile: tests/multiple_files.txt>, <FieldFile: tests/multiple_files_1.txt>, <FieldFile: tests/multiple_files_2.txt>]
++>>> [o.normal for o in objs] # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
++[<FieldFile: tests/multiple_files.txt>,
++ <FieldFile: tests/multiple_files_....txt>,
++ <FieldFile: tests/multiple_files_....txt>]
+ >>> for o in objs:
+ ...     o.delete()
+ 
diff --git a/debian/patches/CVE-2014-0482.patch b/debian/patches/CVE-2014-0482.patch
new file mode 100644
index 0000000..4c53d34
--- /dev/null
+++ b/debian/patches/CVE-2014-0482.patch
@@ -0,0 +1,92 @@
+commit c9e3b9949cd55f090591fbdc4a114fcb8368b6d9
+Author: Preston Holmes <preston at ptone.com>
+Date:   Mon Aug 11 12:04:53 2014 -0400
+
+    [1.4.x] Fixed #23066 -- Modified RemoteUserMiddleware to logout on REMOTE_USE change.
+    
+    This is a security fix. Disclosure following shortly.
+
+Index: python-django-1.2.3/django/contrib/auth/middleware.py
+===================================================================
+--- python-django-1.2.3.orig/django/contrib/auth/middleware.py	2014-08-29 19:49:28.000000000 +0200
++++ python-django-1.2.3/django/contrib/auth/middleware.py	2014-08-29 19:49:28.000000000 +0200
+@@ -1,4 +1,5 @@
+ from django.contrib import auth
++from django.contrib.auth.backends import RemoteUserBackend
+ from django.core.exceptions import ImproperlyConfigured
+ 
+ 
+@@ -48,9 +49,11 @@
+         try:
+             username = request.META[self.header]
+         except KeyError:
+-            # If specified header doesn't exist then return (leaving
+-            # request.user set to AnonymousUser by the
+-            # AuthenticationMiddleware).
++            # If specified header doesn't exist then remove any existing
++            # authenticated remote-user, or return (leaving request.user set to
++            # AnonymousUser by the AuthenticationMiddleware).
++            if request.user.is_authenticated():
++                self._remove_invalid_user(request)
+             return
+         # If the user is already authenticated and that user is the user we are
+         # getting passed in the headers, then the correct user is already
+@@ -58,6 +61,11 @@
+         if request.user.is_authenticated():
+             if request.user.username == self.clean_username(username, request):
+                 return
++            else:
++                # An authenticated user is associated with the request, but
++                # it does not match the authorized user in the header.
++                self._remove_invalid_user(request)
++
+         # We are seeing this user for the first time in this session, attempt
+         # to authenticate the user.
+         user = auth.authenticate(remote_user=username)
+@@ -79,3 +87,17 @@
+         except AttributeError: # Backend has no clean_username method.
+             pass
+         return username
++
++    def _remove_invalid_user(self, request):
++        """
++        Removes the current authenticated user in the request which is invalid
++        but only if the user is authenticated via the RemoteUserBackend.
++        """
++        try:
++            stored_backend = auth.load_backend(request.session.get(auth.BACKEND_SESSION_KEY, ''))
++        except ImproperlyConfigured:
++            # backend failed to load
++            auth.logout(request)
++        else:
++            if isinstance(stored_backend, RemoteUserBackend):
++                auth.logout(request)
+Index: python-django-1.2.3/django/contrib/auth/tests/remote_user.py
+===================================================================
+--- python-django-1.2.3.orig/django/contrib/auth/tests/remote_user.py	2014-08-29 19:49:28.000000000 +0200
++++ python-django-1.2.3/django/contrib/auth/tests/remote_user.py	2014-08-29 19:49:28.000000000 +0200
+@@ -92,6 +92,24 @@
+         response = self.client.get('/remote_user/', REMOTE_USER=self.known_user)
+         self.assertEqual(default_login, response.context['user'].last_login)
+ 
++    def test_user_switch_forces_new_login(self):
++        """
++        Tests that if the username in the header changes between requests
++        that the original user is logged out
++        """
++        User.objects.create(username='knownuser')
++        # Known user authenticates
++        response = self.client.get('/remote_user/',
++                                   **{'REMOTE_USER': self.known_user})
++        self.assertEqual(response.context['user'].username, 'knownuser')
++        # During the session, the REMOTE_USER changes to a different user.
++        response = self.client.get('/remote_user/',
++                                   **{'REMOTE_USER': "newnewuser"})
++        # Ensure that the current user is not the prior remote_user
++        # In backends that create a new user, username is "newnewuser"
++        # In backends that do not create new users, it is '' (anonymous user)
++        self.assertNotEqual(response.context['user'].username, 'knownuser')
++
+     def tearDown(self):
+         """Restores settings to avoid breaking other tests."""
+         settings.MIDDLEWARE_CLASSES = self.curr_middleware
diff --git a/debian/patches/CVE-2014-0483.patch b/debian/patches/CVE-2014-0483.patch
new file mode 100644
index 0000000..ac43bfb
--- /dev/null
+++ b/debian/patches/CVE-2014-0483.patch
@@ -0,0 +1,43 @@
+Description: Prevented reverse() from generating URLs pointing to other hosts
+ Backport of made by Raphaël Hertzog.
+Origin: backport, https://github.com/django/django/commit/c2fe73133b62a1d9e8f7a6b43966570b14618d7e
+Forwarded: not-needed
+Author: Florian Apolloner <florian at apolloner.eu>
+
+--- a/django/core/urlresolvers.py
++++ b/django/core/urlresolvers.py
+@@ -307,6 +307,8 @@ class RegexURLResolver(object):
+                     unicode_kwargs = dict([(k, force_unicode(v)) for (k, v) in kwargs.items()])
+                     candidate = result % unicode_kwargs
+                 if re.search(u'^%s' % pattern, candidate, re.UNICODE):
++                    if candidate.startswith('/'):
++                        candidate = '%%2F%s' % candidate[1:]
+                     return candidate
+         # lookup_view can be URL label, or dotted path, or callable, Any of
+         # these can be passed in at the top, but callables are not friendly in
+--- a/tests/regressiontests/urlpatterns_reverse/tests.py
++++ b/tests/regressiontests/urlpatterns_reverse/tests.py
+@@ -106,7 +106,10 @@ test_data = (
+     ('kwargs_view', '/arg_view/10/', [], {'arg1':10}),
+     ('regressiontests.urlpatterns_reverse.views.absolute_kwargs_view', '/absolute_arg_view/', [], {}),
+     ('regressiontests.urlpatterns_reverse.views.absolute_kwargs_view', '/absolute_arg_view/10/', [], {'arg1':10}),
+-    ('non_path_include', '/includes/non_path_include/', [], {})
++    ('non_path_include', '/includes/non_path_include/', [], {}),
++
++    # Security tests
++    ('security', '/%2Fexample.com/security/', ['/example.com'], {}),
+ 
+ )
+ 
+--- a/tests/regressiontests/urlpatterns_reverse/urls.py
++++ b/tests/regressiontests/urlpatterns_reverse/urls.py
+@@ -63,6 +63,9 @@ urlpatterns = patterns('',
+     
+     url('^includes/', include(other_patterns)),
+ 
++
++    # Security tests
++    url('(.+)/security/$', empty_view, name='security'),
+ )
+ 
+ 
diff --git a/debian/patches/get-random-string-backport.patch b/debian/patches/get-random-string-backport.patch
new file mode 100644
index 0000000..4d56780
--- /dev/null
+++ b/debian/patches/get-random-string-backport.patch
@@ -0,0 +1,53 @@
+Description: Backport get_random_string from django.utils.crypto
+ It's needed by on the the following security fixes.
+Author: Raphaël Hertzog <hertzog at debian.org>, Thorsten Alteholz <alteholz at debian.org>
+Origin: backport, file copy + hand edit
+Last-Update: 2014-09-29
+--- /dev/null
++++ b/django/utils/crypto.py
+@@ -0,0 +1,45 @@
++"""
++Django's standard crypto functions and utilities.
++"""
++
++import hashlib
++import time
++
++# Use the system PRNG if possible
++import random
++try:
++    random = random.SystemRandom()
++    using_sysrandom = True
++except NotImplementedError:
++    import warnings
++    warnings.warn('A secure pseudo-random number generator is not available '
++                  'on your system. Falling back to Mersenne Twister.')
++    using_sysrandom = False
++
++from django.conf import settings
++
++
++def get_random_string(length=12,
++                      allowed_chars='abcdefghijklmnopqrstuvwxyz'
++                                    'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'):
++    """
++    Returns a securely generated random string.
++
++    The default length of 12 with the a-z, A-Z, 0-9 character set returns
++    a 71-bit value. log_2((26+26+10)^12) =~ 71 bits
++    """
++    if not using_sysrandom:
++        # This is ugly, and a hack, but it makes things better than
++        # the alternative of predictability. This re-seeds the PRNG
++        # using a value that is hard for an attacker to predict, every
++        # time a random string is required. This may change the
++        # properties of the chosen random sequence slightly, but this
++        # is better than absolute predictability.
++        random.seed(
++            hashlib.sha256(
++                "%s%s%s" % (
++                    random.getstate(),
++                    time.time(),
++                    settings.SECRET_KEY)
++                ).digest())
++    return ''.join([random.choice(allowed_chars) for i in range(length)])
diff --git a/debian/patches/no-network-access-on-builder.patch b/debian/patches/no-network-access-on-builder.patch
new file mode 100644
index 0000000..d808005
--- /dev/null
+++ b/debian/patches/no-network-access-on-builder.patch
@@ -0,0 +1,30 @@
+Index: python-django-1.2.3/tests/regressiontests/forms/error_messages.py
+===================================================================
+--- python-django-1.2.3.orig/tests/regressiontests/forms/error_messages.py	2010-01-05 04:56:19.000000000 +0100
++++ python-django-1.2.3/tests/regressiontests/forms/error_messages.py	2014-09-18 15:47:17.000000000 +0200
+@@ -224,25 +224,6 @@
+ ...
+ ValidationError: [u'EMPTY FILE']
+ 
+-# URLField ##################################################################
+-
+->>> e = {'required': 'REQUIRED'}
+->>> e['invalid'] = 'INVALID'
+->>> e['invalid_link'] = 'INVALID LINK'
+->>> f = URLField(verify_exists=True, error_messages=e)
+->>> f.clean('')
+-Traceback (most recent call last):
+-...
+-ValidationError: [u'REQUIRED']
+->>> f.clean('abc.c')
+-Traceback (most recent call last):
+-...
+-ValidationError: [u'INVALID']
+->>> f.clean('http://www.broken.djangoproject.com')
+-Traceback (most recent call last):
+-...
+-ValidationError: [u'INVALID LINK']
+-
+ # BooleanField ################################################################
+ 
+ >>> e = {'required': 'REQUIRED'}
diff --git a/debian/patches/series b/debian/patches/series
index 2b3f5c1..b263721 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -31,3 +31,9 @@ CVE-2014-0473.patch
 CVE-2014-0474.patch
 CVE-2014-1418.patch
 CVE-2014-3730.patch
+no-network-access-on-builder.patch
+CVE-2014-0482.patch
+CVE-2014-0483.patch
+CVE-2014-0480.patch
+get-random-string-backport.patch
+CVE-2014-0481.patch

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-django.git



More information about the Python-modules-commits mailing list