diff -Nru python-django-1.7.6/debian/changelog python-django-1.7.6/debian/changelog --- python-django-1.7.6/debian/changelog 2015-03-20 14:21:23.000000000 +0000 +++ python-django-1.7.6/debian/changelog 2015-07-02 15:00:23.000000000 +0000 @@ -1,3 +1,21 @@ +python-django (1.7.6-1ubuntu2.1) vivid-security; urgency=medium + + * SECURITY UPDATE: denial of service via empty session records + - debian/patches/CVE-2015-5143.patch: avoid creating a session record + when loading the session in + django/contrib/sessions/backends/cache.py, + django/contrib/sessions/backends/cached_db.py, + django/contrib/sessions/backends/db.py, + django/contrib/sessions/backends/file.py, + added test to django/contrib/sessions/tests.py. + - CVE-2015-5143 + * SECURITY UPDATE: header injection via newlines + - debian/patches/CVE-2015-5144.patch: check for newlines in + django/core/validators.py, added tests to tests/validators/tests.py. + - CVE-2015-5144 + + -- Marc Deslauriers Thu, 02 Jul 2015 10:53:48 -0400 + python-django (1.7.6-1ubuntu2) vivid; urgency=medium * SECURITY UPDATE: denial-of-service possibility with strip_tags diff -Nru python-django-1.7.6/debian/patches/CVE-2015-5143.patch python-django-1.7.6/debian/patches/CVE-2015-5143.patch --- python-django-1.7.6/debian/patches/CVE-2015-5143.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.7.6/debian/patches/CVE-2015-5143.patch 2015-07-02 14:53:39.000000000 +0000 @@ -0,0 +1,155 @@ +commit ac4a54705fb9cdde832d07667843b45b208f9aad +Author: Carl Meyer +Date: Wed Jun 10 15:45:20 2015 -0600 + + [1.7.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. + +diff --git a/django/contrib/sessions/backends/cache.py b/django/contrib/sessions/backends/cache.py +index b1058b3..faeb106 100644 +--- a/django/contrib/sessions/backends/cache.py ++++ b/django/contrib/sessions/backends/cache.py +@@ -27,7 +27,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): +@@ -49,6 +49,8 @@ class SessionStore(SessionBase): + "It is likely that the cache is unavailable.") + + def save(self, must_create=False): ++ if self.session_key is None: ++ return self.create() + if must_create: + func = self._cache.add + else: +@@ -60,7 +62,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: +diff --git a/django/contrib/sessions/backends/cached_db.py b/django/contrib/sessions/backends/cached_db.py +index f5c14b0..5cc6f79 100644 +--- a/django/contrib/sessions/backends/cached_db.py ++++ b/django/contrib/sessions/backends/cached_db.py +@@ -51,12 +51,12 @@ class SessionStore(DBStore): + logger = logging.getLogger('django.security.%s' % + e.__class__.__name__) + logger.warning(force_text(e)) +- self.create() ++ self._session_key = None + data = {} + return data + + def exists(self, session_key): +- if (KEY_PREFIX + session_key) in self._cache: ++ if session_key and (KEY_PREFIX + session_key) in self._cache: + return True + return super(SessionStore, self).exists(session_key) + +diff --git a/django/contrib/sessions/backends/db.py b/django/contrib/sessions/backends/db.py +index a087061..3e6cdf9 100644 +--- a/django/contrib/sessions/backends/db.py ++++ b/django/contrib/sessions/backends/db.py +@@ -26,7 +26,7 @@ class SessionStore(SessionBase): + logger = logging.getLogger('django.security.%s' % + e.__class__.__name__) + logger.warning(force_text(e)) +- self.create() ++ self._session_key = None + return {} + + def exists(self, session_key): +@@ -43,7 +43,6 @@ class SessionStore(SessionBase): + # Key wasn't unique. Try again. + continue + self.modified = True +- self._session_cache = {} + return + + def save(self, must_create=False): +@@ -53,6 +52,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)), +diff --git a/django/contrib/sessions/backends/file.py b/django/contrib/sessions/backends/file.py +index 6569daf..f886bcd 100644 +--- a/django/contrib/sessions/backends/file.py ++++ b/django/contrib/sessions/backends/file.py +@@ -96,7 +96,7 @@ class SessionStore(SessionBase): + self.delete() + self.create() + except (IOError, SuspiciousOperation): +- self.create() ++ self._session_key = None + return session_data + + def create(self): +@@ -107,10 +107,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) +diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py +index 8d63aaa..6e042c7 100644 +--- a/django/contrib/sessions/tests.py ++++ b/django/contrib/sessions/tests.py +@@ -171,6 +171,11 @@ class SessionTestsMixin(object): + self.assertNotEqual(self.session.session_key, prev_key) + self.assertEqual(list(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. +@@ -306,6 +311,21 @@ class SessionTestsMixin(object): + self.session.delete(old_session_key) + self.session.delete(new_session_key) + ++ 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('someunknownkey') ++ session.load() ++ ++ self.assertFalse(session.exists(session.session_key)) ++ # provided unknown key was cycled, not reused ++ self.assertNotEqual(session.session_key, 'someunknownkey') ++ + + class DatabaseSessionTests(SessionTestsMixin, TestCase): + diff -Nru python-django-1.7.6/debian/patches/CVE-2015-5144.patch python-django-1.7.6/debian/patches/CVE-2015-5144.patch --- python-django-1.7.6/debian/patches/CVE-2015-5144.patch 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.7.6/debian/patches/CVE-2015-5144.patch 2015-07-02 14:53:43.000000000 +0000 @@ -0,0 +1,149 @@ +commit 6e4164b083adb5c974c7ded0f3aeae5188e52b5a +Author: Tim Graham +Date: Fri Jun 12 13:49:31 2015 -0400 + + [1.7.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. + +diff --git a/django/core/validators.py b/django/core/validators.py +index 1e599ec..462e310 100644 +--- a/django/core/validators.py ++++ b/django/core/validators.py +@@ -73,7 +73,7 @@ class URLValidator(RegexValidator): + r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4 + r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6 + r'(?::\d+)?' # optional port +- r'(?:/?|[/?]\S+)$', re.IGNORECASE) ++ r'(?:/?|[/?]\S+)\Z', re.IGNORECASE) + message = _('Enter a valid URL.') + schemes = ['http', 'https', 'ftp', 'ftps'] + +@@ -107,12 +107,15 @@ class URLValidator(RegexValidator): + else: + url = value + ++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(_('Enter a valid integer.'), code='invalid') ++ return integer_validator(value) + + + @deconstructible +@@ -120,15 +123,15 @@ class EmailValidator(object): + message = _('Enter a valid email address.') + code = 'invalid' + user_regex = 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"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*\Z" # dot-atom ++ r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"\Z)', # quoted-string + re.IGNORECASE) + domain_regex = re.compile( +- r'(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}|[A-Z0-9-]{2,}(?