[Python-modules-commits] [python-django] 03/03: Imported Debian patch 1.4.5-1+deb7u12

Luke Faraone lfaraone at moszumanska.debian.org
Sun Jul 12 01:25:15 UTC 2015


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

lfaraone pushed a commit to branch debian/wheezy
in repository python-django.

commit 0a9921ee164dd4bb8d435c320efb7362b895652f
Author: Luke Faraone <lfaraone at debian.org>
Date:   Tue Jul 7 05:31:32 2015 +0000

    Imported Debian patch 1.4.5-1+deb7u12
---
 debian/changelog                   |   8 ++
 debian/patches/newlines-1.4.x.diff | 145 +++++++++++++++++++++++++++++++++
 debian/patches/series              |   2 +
 debian/patches/session-1.4.x.diff  | 159 +++++++++++++++++++++++++++++++++++++
 4 files changed, 314 insertions(+)

diff --git a/debian/changelog b/debian/changelog
index 34af7a2..d533e5e 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,11 @@
+python-django (1.4.5-1+deb7u12) wheezy-security; urgency=high
+
+  * New upstream security release:
+    - Possible denial-of-service via session store (CVE-2015-5143)
+    - Possible email header injection via newlines (CVE-2015-5144)
+
+ -- Luke Faraone <lfaraone at debian.org>  Tue, 07 Jul 2015 05:31:32 +0000
+
 python-django (1.4.5-1+deb7u11) wheezy-security; urgency=high
 
   * Non-maintainer upload by the Security Team.
diff --git a/debian/patches/newlines-1.4.x.diff b/debian/patches/newlines-1.4.x.diff
new file mode 100644
index 0000000..278bb86
--- /dev/null
+++ b/debian/patches/newlines-1.4.x.diff
@@ -0,0 +1,145 @@
+commit 55fa3c3f68a07b737290b1e25b1ed0664fff6a96
+Author: Tim Graham <timograham at gmail.com>
+Date:   Fri Jun 12 13:49:31 2015 -0400
+
+    [1.4.x] Prevented newlines from being accepted in some validators.
+    
+    This is a security fix; disclosure to follow shortly.
+    
+    Thanks to Sjoerd Job Postmus for the report and draft patch.
+
+Index: python-django-1.4.5/django/core/validators.py
+===================================================================
+--- python-django-1.4.5.orig/django/core/validators.py
++++ python-django-1.4.5/django/core/validators.py
+@@ -50,7 +50,7 @@ class URLValidator(RegexValidator):
+         r'localhost|' #localhost...
+         r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
+         r'(?::\d+)?' # optional port
+-        r'(?:/?|[/?]\S+)$', re.IGNORECASE)
++        r'(?:/?|[/?]\S+)\Z', re.IGNORECASE)
+ 
+     def __init__(self, verify_exists=False,
+                  validator_user_agent=URL_VALIDATOR_USER_AGENT):
+@@ -133,11 +133,16 @@ class URLValidator(RegexValidator):
+                 raise broken_error
+ 
+ 
++integer_validator = RegexValidator(
++    re.compile('^-?\d+\Z'),
++    message=_('Enter a valid integer.'),
++    code='invalid',
++)
++
++
+ def validate_integer(value):
+-    try:
+-        int(value)
+-    except (ValueError, TypeError):
+-        raise ValidationError('')
++    return integer_validator(value)
++
+ 
+ class EmailValidator(RegexValidator):
+ 
+@@ -160,14 +165,14 @@ email_re = re.compile(
+     r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom
+     # quoted-string, see also http://tools.ietf.org/html/rfc2822#section-3.2.5
+     r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"'
+-    r')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$)'  # domain
+-    r'|\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]$', re.IGNORECASE)  # literal form, ipv4 address (SMTP 4.1.3)
++    r')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?\Z)'  # domain
++    r'|\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]\Z', re.IGNORECASE)  # literal form, ipv4 address (SMTP 4.1.3)
+ validate_email = EmailValidator(email_re, _(u'Enter a valid e-mail address.'), 'invalid')
+ 
+-slug_re = re.compile(r'^[-\w]+$')
++slug_re = re.compile(r'^[-\w]+\Z')
+ validate_slug = RegexValidator(slug_re, _(u"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."), 'invalid')
+ 
+-ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$')
++ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\Z')
+ validate_ipv4_address = RegexValidator(ipv4_re, _(u'Enter a valid IPv4 address.'), 'invalid')
+ 
+ def validate_ipv6_address(value):
+@@ -205,7 +210,7 @@ def ip_address_validators(protocol, unpa
+         raise ValueError("The protocol '%s' is unknown. Supported: %s"
+                          % (protocol, ip_address_validator_map.keys()))
+ 
+-comma_separated_int_list_re = re.compile('^[\d,]+$')
++comma_separated_int_list_re = re.compile('^[\d,]+\Z')
+ validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_re, _(u'Enter only digits separated by commas.'), 'invalid')
+ 
+ 
+@@ -249,4 +254,3 @@ class MaxLengthValidator(BaseValidator):
+     clean   = lambda self, x: len(x)
+     message = _(u'Ensure this value has at most %(limit_value)d characters (it has %(show_value)d).')
+     code = 'max_length'
+-
+Index: python-django-1.4.5/tests/modeltests/validators/tests.py
+===================================================================
+--- python-django-1.4.5.orig/tests/modeltests/validators/tests.py
++++ python-django-1.4.5/tests/modeltests/validators/tests.py
+@@ -12,13 +12,16 @@ NOW = datetime.now()
+ 
+ TEST_DATA = (
+     # (validator, value, expected),
++    # (validator, value, expected),
+     (validate_integer, '42', None),
+     (validate_integer, '-42', None),
+     (validate_integer, -42, None),
+-    (validate_integer, -42.5, None),
+ 
++    (validate_integer, -42.5, ValidationError),
+     (validate_integer, None, ValidationError),
+     (validate_integer, 'a', ValidationError),
++    (validate_integer, '\n42', ValidationError),
++    (validate_integer, '42\n', ValidationError),
+ 
+     (validate_email, 'email at here.com', None),
+     (validate_email, 'weirder-email at here.and.there.com', None),
+@@ -33,6 +36,11 @@ TEST_DATA = (
+     # Quoted-string format (CR not allowed)
+     (validate_email, '"\\\011"@here.com', None),
+     (validate_email, '"\\\012"@here.com', ValidationError),
++    # Trailing newlines in username or domain not allowed
++    (validate_email, 'a at b.com\n', ValidationError),
++    (validate_email, 'a\n at b.com', ValidationError),
++    (validate_email, '"test at test"\n at example.com', ValidationError),
++    (validate_email, 'a@[127.0.0.1]\n', ValidationError),
+ 
+     (validate_slug, 'slug-ok', None),
+     (validate_slug, 'longer-slug-still-ok', None),
+@@ -45,6 +53,7 @@ TEST_DATA = (
+     (validate_slug, 'some at mail.com', ValidationError),
+     (validate_slug, '你好', ValidationError),
+     (validate_slug, '\n', ValidationError),
++    (validate_slug, 'trailing-newline\n', ValidationError),
+ 
+     (validate_ipv4_address, '1.1.1.1', None),
+     (validate_ipv4_address, '255.0.0.0', None),
+@@ -54,6 +63,7 @@ TEST_DATA = (
+     (validate_ipv4_address, '25.1.1.', ValidationError),
+     (validate_ipv4_address, '25,1,1,1', ValidationError),
+     (validate_ipv4_address, '25.1 .1.1', ValidationError),
++    (validate_ipv4_address, '1.1.1.1\n', ValidationError),
+ 
+     # validate_ipv6_address uses django.utils.ipv6, which
+     # is tested in much greater detail in it's own testcase
+@@ -87,6 +97,7 @@ TEST_DATA = (
+     (validate_comma_separated_integer_list, '', ValidationError),
+     (validate_comma_separated_integer_list, 'a,b,c', ValidationError),
+     (validate_comma_separated_integer_list, '1, 2, 3', ValidationError),
++    (validate_comma_separated_integer_list, '1,2,3\n', ValidationError),
+ 
+     (MaxValueValidator(10), 10, None),
+     (MaxValueValidator(10), -10, None),
+@@ -138,6 +149,9 @@ TEST_DATA = (
+     (URLValidator(), 'http://-invalid.com', ValidationError),
+     (URLValidator(), 'http://inv-.alid-.com', ValidationError),
+     (URLValidator(), 'http://inv-.-alid.com', ValidationError),
++    # Trailing newlines not accepted
++    (URLValidator(), 'http://www.djangoproject.com/\n', ValidationError),
++    (URLValidator(), 'http://[::ffff:192.9.5.5]\n', ValidationError),
+ 
+     (BaseValidator(True), True, None),
+     (BaseValidator(True), False, ValidationError),
diff --git a/debian/patches/series b/debian/patches/series
index f2c2b83..365e455 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -23,3 +23,5 @@ CVE-2015-0220.diff
 CVE-2015-0221.diff
 CVE-2015-0221-regression-fix.diff
 CVE-2015-2317.diff
+session-1.4.x.diff
+newlines-1.4.x.diff
diff --git a/debian/patches/session-1.4.x.diff b/debian/patches/session-1.4.x.diff
new file mode 100644
index 0000000..5707329
--- /dev/null
+++ b/debian/patches/session-1.4.x.diff
@@ -0,0 +1,159 @@
+commit cc0777db672f6edd44a6a3f5b4684bb3a71d11d3
+Author: Carl Meyer <carl at oddbird.net>
+Date:   Wed Jun 10 15:45:20 2015 -0600
+
+    [1.4.x] Fixed #19324 -- Avoided creating a session record when loading the session.
+    
+    The session record is now only created if/when the session is modified. This
+    prevents a potential DoS via creation of many empty session records.
+    
+    This is a security fix; disclosure to follow shortly.
+
+Index: python-django-1.4.5/django/contrib/sessions/backends/cache.py
+===================================================================
+--- python-django-1.4.5.orig/django/contrib/sessions/backends/cache.py
++++ python-django-1.4.5/django/contrib/sessions/backends/cache.py
+@@ -25,7 +25,7 @@ class SessionStore(SessionBase):
+             session_data = None
+         if session_data is not None:
+             return session_data
+-        self.create()
++        self._session_key = None
+         return {}
+ 
+     def create(self):
+@@ -45,6 +45,8 @@ class SessionStore(SessionBase):
+         raise RuntimeError("Unable to create a new session key.")
+ 
+     def save(self, must_create=False):
++        if self.session_key is None:
++            return self.create()
+         if must_create:
+             func = self._cache.add
+         else:
+@@ -56,7 +58,7 @@ class SessionStore(SessionBase):
+             raise CreateError
+ 
+     def exists(self, session_key):
+-        return (KEY_PREFIX + session_key) in self._cache
++        return session_key and (KEY_PREFIX + session_key) in self._cache
+ 
+     def delete(self, session_key=None):
+         if session_key is None:
+Index: python-django-1.4.5/django/contrib/sessions/backends/cached_db.py
+===================================================================
+--- python-django-1.4.5.orig/django/contrib/sessions/backends/cached_db.py
++++ python-django-1.4.5/django/contrib/sessions/backends/cached_db.py
+@@ -30,11 +30,12 @@ class SessionStore(DBStore):
+             data = None
+         if data is None:
+             data = super(SessionStore, self).load()
+-            cache.set(self.cache_key, data, settings.SESSION_COOKIE_AGE)
++            if self.session_key:
++                cache.set(self.cache_key, data, settings.SESSION_COOKIE_AGE)
+         return data
+ 
+     def exists(self, session_key):
+-        if (KEY_PREFIX + session_key) in cache:
++        if session_key and (KEY_PREFIX + session_key) in cache:
+             return True
+         return super(SessionStore, self).exists(session_key)
+ 
+Index: python-django-1.4.5/django/contrib/sessions/backends/db.py
+===================================================================
+--- python-django-1.4.5.orig/django/contrib/sessions/backends/db.py
++++ python-django-1.4.5/django/contrib/sessions/backends/db.py
+@@ -20,7 +20,7 @@ class SessionStore(SessionBase):
+             )
+             return self.decode(force_unicode(s.session_data))
+         except (Session.DoesNotExist, SuspiciousOperation):
+-            self.create()
++            self._session_key = None
+             return {}
+ 
+     def exists(self, session_key):
+@@ -37,7 +37,6 @@ class SessionStore(SessionBase):
+                 # Key wasn't unique. Try again.
+                 continue
+             self.modified = True
+-            self._session_cache = {}
+             return
+ 
+     def save(self, must_create=False):
+@@ -47,6 +46,8 @@ class SessionStore(SessionBase):
+         create a *new* entry (as opposed to possibly updating an existing
+         entry).
+         """
++        if self.session_key is None:
++            return self.create()
+         obj = Session(
+             session_key=self._get_or_create_session_key(),
+             session_data=self.encode(self._get_session(no_load=must_create)),
+Index: python-django-1.4.5/django/contrib/sessions/backends/file.py
+===================================================================
+--- python-django-1.4.5.orig/django/contrib/sessions/backends/file.py
++++ python-django-1.4.5/django/contrib/sessions/backends/file.py
+@@ -56,11 +56,11 @@ class SessionStore(SessionBase):
+                     try:
+                         session_data = self.decode(file_data)
+                     except (EOFError, SuspiciousOperation):
+-                        self.create()
++                        self._session_key = None
+             finally:
+                 session_file.close()
+         except IOError:
+-            self.create()
++            self._session_key = None
+         return session_data
+ 
+     def create(self):
+@@ -71,10 +71,11 @@ class SessionStore(SessionBase):
+             except CreateError:
+                 continue
+             self.modified = True
+-            self._session_cache = {}
+             return
+ 
+     def save(self, must_create=False):
++        if self.session_key is None:
++            return self.create()
+         # Get the session data now, before we start messing
+         # with the file it is stored within.
+         session_data = self._get_session(no_load=must_create)
+Index: python-django-1.4.5/django/contrib/sessions/tests.py
+===================================================================
+--- python-django-1.4.5.orig/django/contrib/sessions/tests.py
++++ python-django-1.4.5/django/contrib/sessions/tests.py
+@@ -162,6 +162,11 @@ class SessionTestsMixin(object):
+         self.assertNotEqual(self.session.session_key, prev_key)
+         self.assertEqual(self.session.items(), prev_data)
+ 
++    def test_save_doesnt_clear_data(self):
++        self.session['a'] = 'b'
++        self.session.save()
++        self.assertEqual(self.session['a'], 'b')
++
+     def test_invalid_key(self):
+         # Submitting an invalid session key (either by guessing, or if the db has
+         # removed the key) results in a new key being generated.
+@@ -256,6 +261,20 @@ class SessionTestsMixin(object):
+         encoded = self.session.encode(data)
+         self.assertEqual(self.session.decode(encoded), data)
+ 
++    def test_session_load_does_not_create_record(self):
++        """
++        Loading an unknown session key does not create a session record.
++
++        Creating session records on load is a DOS vulnerability.
++        """
++        if self.backend is CookieSession:
++            raise unittest.SkipTest("Cookie backend doesn't have an external store to create records in.")
++        session = self.backend('deadbeef')
++        session.load()
++
++        self.assertFalse(session.exists(session.session_key))
++        # provided unknown key was cycled, not reused
++        self.assertNotEqual(session.session_key, 'deadbeef')
+ 
+ class DatabaseSessionTests(SessionTestsMixin, TestCase):
+ 

-- 
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