[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