[Python-modules-commits] [python-django] 02/04: Add two more patches fixing CVE-2015-5143 and CVE-2015-5144

Raphaël Hertzog hertzog at moszumanska.debian.org
Thu Jul 16 13:01:53 UTC 2015


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 f1c233d4890d46488d5cd8ec893ee3eb4f05714d
Author: Raphaël Hertzog <hertzog at debian.org>
Date:   Wed Jul 15 17:55:59 2015 +0200

    Add two more patches fixing CVE-2015-5143 and CVE-2015-5144
---
 debian/changelog                   |   4 +
 debian/patches/CVE-2015-5143.patch | 238 +++++++++++++++++++++++++++++++++++++
 debian/patches/CVE-2015-5144.patch | 133 +++++++++++++++++++++
 debian/patches/series              |   2 +
 4 files changed, 377 insertions(+)

diff --git a/debian/changelog b/debian/changelog
index b18e385..42de5b0 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -3,6 +3,10 @@ python-django (1.2.3-3+squeeze13) UNRELEASED; urgency=medium
   * Backport multiple security fixes released in 1.4 branch:
     https://www.djangoproject.com/weblog/2015/mar/18/security-releases/
      - Possible XSS attack via user-supplied redirect URLs (CVE-2015-2317)
+    https://www.djangoproject.com/weblog/2015/jul/08/security-releases/
+     - Denial-of-service possibility by filling session store (CVE-2015-5143)
+     - Header injection possibility since validators accept newlines in input
+       (CVE-2015-5144)
 
  -- Raphaël Hertzog <hertzog at debian.org>  Wed, 15 Jul 2015 16:26:30 +0200
 
diff --git a/debian/patches/CVE-2015-5143.patch b/debian/patches/CVE-2015-5143.patch
new file mode 100644
index 0000000..b37114d
--- /dev/null
+++ b/debian/patches/CVE-2015-5143.patch
@@ -0,0 +1,238 @@
+From 2e47f3e401c29bc2ba5ab794d483cb0820855fb9 Mon Sep 17 00:00:00 2001
+From: Carl Meyer <carl at oddbird.net>
+Date: Wed, 10 Jun 2015 15:45:20 -0600
+Subject: [PATCH] [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.
+---
+ django/contrib/sessions/backends/cache.py     |  6 ++++--
+ django/contrib/sessions/backends/cached_db.py |  5 +++--
+ django/contrib/sessions/backends/db.py        |  5 +++--
+ django/contrib/sessions/backends/file.py      |  7 ++++---
+ django/contrib/sessions/tests.py              | 19 +++++++++++++++++++
+ docs/releases/1.4.21.txt                      | 21 +++++++++++++++++++++
+ 6 files changed, 54 insertions(+), 9 deletions(-)
+
+--- a/django/contrib/sessions/backends/cache.py
++++ b/django/contrib/sessions/backends/cache.py
+@@ -15,7 +15,7 @@ class SessionStore(SessionBase):
+         session_data = self._cache.get(KEY_PREFIX + self.session_key)
+         if session_data is not None:
+             return session_data
+-        self.create()
++        self._session_key = None
+         return {}
+ 
+     def create(self):
+@@ -35,6 +35,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:
+@@ -45,7 +47,7 @@ class SessionStore(SessionBase):
+             raise CreateError
+ 
+     def exists(self, session_key):
+-        if self._cache.has_key(KEY_PREFIX + session_key):
++        if session_key and self._cache.has_key(KEY_PREFIX + session_key):
+             return True
+         return False
+ 
+--- a/django/contrib/sessions/backends/cached_db.py
++++ b/django/contrib/sessions/backends/cached_db.py
+@@ -20,16 +20,19 @@ class SessionStore(DBStore):
+         data = cache.get(KEY_PREFIX + self.session_key, None)
+         if data is None:
+             data = super(SessionStore, self).load()
+-            cache.set(KEY_PREFIX + self.session_key, data, 
+-                      settings.SESSION_COOKIE_AGE)
++            if self.session_key:
++                cache.set(KEY_PREFIX + self.session_key, data,
++                          settings.SESSION_COOKIE_AGE)
+         return data
+ 
+     def exists(self, session_key):
++        if session_key and cache.has_key(KEY_PREFIX + session_key):
++            return True
+         return super(SessionStore, self).exists(session_key)
+ 
+     def save(self, must_create=False):
+         super(SessionStore, self).save(must_create)
+-        cache.set(KEY_PREFIX + self.session_key, self._session, 
++        cache.set(KEY_PREFIX + self.session_key, self._session,
+                   settings.SESSION_COOKIE_AGE)
+ 
+     def delete(self, session_key=None):
+@@ -43,4 +46,4 @@ class SessionStore(DBStore):
+         """
+         self.clear()
+         self.delete(self.session_key)
+-        self.create()
+\ No newline at end of file
++        self.create()
+--- a/django/contrib/sessions/backends/db.py
++++ b/django/contrib/sessions/backends/db.py
+@@ -21,7 +21,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):
+@@ -42,7 +42,6 @@ class SessionStore(SessionBase):
+                 # Key wasn't unique. Try again.
+                 continue
+             self.modified = True
+-            self._session_cache = {}
+             return
+ 
+     def save(self, must_create=False):
+@@ -52,6 +51,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.session_key,
+             session_data = self.encode(self._get_session(no_load=must_create)),
+--- a/django/contrib/sessions/backends/file.py
++++ b/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:
+-            pass
++            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)
+--- a/django/contrib/sessions/tests.py
++++ b/django/contrib/sessions/tests.py
+@@ -40,6 +40,8 @@ False
+ (True, True)
+ >>> db_session['a'], db_session['b'] = 'c', 'd'
+ >>> db_session.save()
++>>> db_session['a']
++'c'
+ >>> prev_key = db_session.session_key
+ >>> prev_data = db_session.items()
+ >>> db_session.cycle_key()
+@@ -55,6 +57,16 @@ True
+ >>> db_session.save()
+ >>> DatabaseSession('1').get('cat')
+ 
++# Loading an unknown session key does not create a session record
++# Creating session records on load is a DOS vulnerability.
++>>> session = DatabaseSession('deadbeef')
++>>> session.load()
++{}
++>>> session.exists(session.session_key)
++False
++>>> session.session_key != 'deadbeef'
++True
++
+ #
+ # Cached DB session tests
+ #
+@@ -75,6 +87,20 @@ True
+ >>> cdb_session.delete(cdb_session.session_key)
+ >>> cdb_session.exists(cdb_session.session_key)
+ False
++>>> cdb_session['a'] = 'b'
++>>> cdb_session.save()
++>>> cdb_session['a']
++'b'
++
++# Loading an unknown session key does not create a session record
++# Creating session records on load is a DOS vulnerability.
++>>> session = CacheDBSession('deadbeef')
++>>> session.load()
++{}
++>>> session.exists(session.session_key)
++False
++>>> session.session_key != 'deadbeef'
++True
+ 
+ #
+ # File session tests.
+@@ -117,6 +143,8 @@ False
+ (True, True)
+ >>> file_session['a'], file_session['b'] = 'c', 'd'
+ >>> file_session.save()
++>>> file_session['a']
++'c'
+ >>> prev_key = file_session.session_key
+ >>> prev_data = file_session.items()
+ >>> file_session.cycle_key()
+@@ -129,6 +157,16 @@ True
+ >>> file_session = FileSession(file_session.session_key)
+ >>> file_session.save()
+ 
++# Loading an unknown session key does not create a session record
++# Creating session records on load is a DOS vulnerability.
++>>> session = FileSession('deadbeef')
++>>> session.load()
++{}
++>>> session.exists(session.session_key)
++False
++>>> session.session_key != 'deadbeef'
++True
++
+ # Ensure we don't allow directory traversal
+ >>> FileSession("a/b/c").load()
+ Traceback (innermost last):
+@@ -186,6 +224,8 @@ False
+ (True, True)
+ >>> cache_session['a'], cache_session['b'] = 'c', 'd'
+ >>> cache_session.save()
++>>> cache_session['a']
++'c'
+ >>> prev_key = cache_session.session_key
+ >>> prev_data = cache_session.items()
+ >>> cache_session.cycle_key()
+@@ -204,6 +244,16 @@ True
+ >>> cache_session.save()
+ >>> cache_session.delete(cache_session.session_key)
+ 
++# Loading an unknown session key does not create a session record
++# Creating session records on load is a DOS vulnerability.
++>>> session = CacheSession('deadbeef')
++>>> session.load()
++{}
++>>> session.exists(session.session_key)
++False
++>>> session.session_key != 'deadbeef'
++True
++
+ >>> s = SessionBase()
+ >>> s._session['some key'] = 'exists' # Pre-populate the session with some data
+ >>> s.accessed = False   # Reset to pretend this wasn't accessed previously
diff --git a/debian/patches/CVE-2015-5144.patch b/debian/patches/CVE-2015-5144.patch
new file mode 100644
index 0000000..81aed6c
--- /dev/null
+++ b/debian/patches/CVE-2015-5144.patch
@@ -0,0 +1,133 @@
+From 1ba1cdce7d58e6740fe51955d945b56ae51d072a Mon Sep 17 00:00:00 2001
+From: Tim Graham <timograham at gmail.com>
+Date: Fri, 12 Jun 2015 13:49:31 -0400
+Subject: [PATCH] [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.
+---
+ django/core/validators.py            | 26 +++++++++++++++-----------
+ docs/releases/1.4.21.txt             | 26 ++++++++++++++++++++++++++
+ tests/modeltests/validators/tests.py | 16 +++++++++++++++-
+ 3 files changed, 56 insertions(+), 12 deletions(-)
+
+--- a/django/core/validators.py
++++ b/django/core/validators.py
+@@ -45,7 +45,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):
+         super(URLValidator, self).__init__()
+@@ -89,11 +89,16 @@ class URLValidator(RegexValidator):
+                 raise ValidationError(_(u'This URL appears to be a broken link.'), code='invalid_link')
+ 
+ 
++integer_validator = RegexValidator(
++    re.compile('^-?\d+\Z'),
++    message=_('Enter a valid integer.'),
++    code='invalid',
++)
++
++
+ def validate_integer(value):
+-    try:
+-        int(value)
+-    except (ValueError, TypeError), e:
+-        raise ValidationError('')
++    return integer_validator(value)
++
+ 
+ class EmailValidator(RegexValidator):
+ 
+@@ -116,16 +121,16 @@ class EmailValidator(RegexValidator):
+ email_re = re.compile(
+     r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom
+     r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string
+-    r')@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$', re.IGNORECASE)  # domain
++    r')@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$\Z', re.IGNORECASE)  # domain
+ 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')
+ 
+-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')
+ 
+ 
+--- a/tests/modeltests/validators/tests.py
++++ b/tests/modeltests/validators/tests.py
+@@ -10,13 +10,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),
+@@ -26,6 +29,10 @@ TEST_DATA = (
+     (validate_email, 'abc', ValidationError),
+     (validate_email, 'a @x.cz', ValidationError),
+     (validate_email, 'something@@somewhere.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_slug, 'slug-ok', None),
+     (validate_slug, 'longer-slug-still-ok', None),
+@@ -38,6 +45,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),
+@@ -47,6 +55,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_comma_separated_integer_list, '1', None),
+     (validate_comma_separated_integer_list, '1,2,3', None),
+@@ -55,6 +64,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),
+@@ -106,6 +116,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 5cc19af..4bc311d 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -42,3 +42,5 @@ CVE-2015-0220.diff
 CVE-2015-0221.diff
 CVE-2015-0221-regression-fix.diff
 CVE-2015-2317.patch
+CVE-2015-5143.patch
+CVE-2015-5144.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