[Python-modules-commits] [python-django] 01/01: Imported Debian patch 1.7.7-1+deb8u1
Luke Faraone
lfaraone at moszumanska.debian.org
Sun Jul 12 01:20:10 UTC 2015
This is an automated email from the git hooks/post-receive script.
lfaraone pushed a commit to branch debian/jessie
in repository python-django.
commit c46d924c8c166baac1d780928685aae6a6be852d
Author: Luke Faraone <lfaraone at debian.org>
Date: Tue Jul 7 05:07:10 2015 +0000
Imported Debian patch 1.7.7-1+deb8u1
---
debian/changelog | 8 ++
debian/patches/newlines-1.7.x.diff | 149 +++++++++++++++++++++++++++++++++++
debian/patches/series | 2 +
debian/patches/session-1.7.x.diff | 155 +++++++++++++++++++++++++++++++++++++
4 files changed, 314 insertions(+)
diff --git a/debian/changelog b/debian/changelog
index 39822bc..6003306 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,11 @@
+python-django (1.7.7-1+deb8u1) jessie-security; urgency=high
+
+ * SECURITY UPDATE:
+ - CVE-2015-5143: possible denial-of-service via session store
+ - CVE-2015-5144: email header injection via newlines
+
+ -- Luke Faraone <lfaraone at debian.org> Tue, 07 Jul 2015 05:07:10 +0000
+
python-django (1.7.7-1) unstable; urgency=high
* New upstream security and bugfix release:
diff --git a/debian/patches/newlines-1.7.x.diff b/debian/patches/newlines-1.7.x.diff
new file mode 100644
index 0000000..78c320e
--- /dev/null
+++ b/debian/patches/newlines-1.7.x.diff
@@ -0,0 +1,149 @@
+commit 6e4164b083adb5c974c7ded0f3aeae5188e52b5a
+Author: Tim Graham <timograham at gmail.com>
+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.
+
+Index: python-django-1.7.7/django/core/validators.py
+===================================================================
+--- python-django-1.7.7.orig/django/core/validators.py
++++ python-django-1.7.7/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,}(?<!-))$',
++ r'(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}|[A-Z0-9-]{2,}(?<!-))\Z',
+ re.IGNORECASE)
+ literal_regex = re.compile(
+ # literal form, ipv4 or ipv6 address (SMTP 4.1.3)
+- r'\[([A-f0-9:\.]+)\]$',
++ r'\[([A-f0-9:\.]+)\]\Z',
+ re.IGNORECASE)
+ domain_whitelist = ['localhost']
+
+@@ -181,10 +184,10 @@ class EmailValidator(object):
+
+ validate_email = EmailValidator()
+
+-slug_re = re.compile(r'^[-a-zA-Z0-9_]+$')
++slug_re = re.compile(r'^[-a-zA-Z0-9_]+\Z')
+ validate_slug = RegexValidator(slug_re, _("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, _('Enter a valid IPv4 address.'), 'invalid')
+
+
+@@ -225,7 +228,7 @@ def ip_address_validators(protocol, unpa
+ raise ValueError("The protocol '%s' is unknown. Supported: %s"
+ % (protocol, list(ip_address_validator_map)))
+
+-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, _('Enter only digits separated by commas.'), 'invalid')
+
+
+Index: python-django-1.7.7/tests/validators/tests.py
+===================================================================
+--- python-django-1.7.7.orig/tests/validators/tests.py
++++ python-django-1.7.7/tests/validators/tests.py
+@@ -25,10 +25,12 @@ TEST_DATA = (
+ (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),
+@@ -66,6 +68,11 @@ TEST_DATA = (
+ (validate_email, '"\\\011"@here.com', None),
+ (validate_email, '"\\\012"@here.com', ValidationError),
+ (validate_email, 'trailingdot at shouldfail.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),
+@@ -78,6 +85,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),
+@@ -87,6 +95,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 its own testcase
+@@ -120,6 +129,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),
+@@ -181,6 +191,9 @@ TEST_DATA = (
+ (URLValidator(), 'file://localhost/path', ValidationError),
+ (URLValidator(), 'git://example.com/', ValidationError),
+ (URLValidator(EXTENDED_SCHEMES), 'git://-invalid.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 c73a668..bf0f6bd 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1,3 +1,5 @@
02_disable-sources-in-sphinxdoc.diff
03_manpage.diff
06_use_debian_geoip_database_as_default.diff
+newlines-1.7.x.diff
+session-1.7.x.diff
diff --git a/debian/patches/session-1.7.x.diff b/debian/patches/session-1.7.x.diff
new file mode 100644
index 0000000..8b3ddfb
--- /dev/null
+++ b/debian/patches/session-1.7.x.diff
@@ -0,0 +1,155 @@
+commit ac4a54705fb9cdde832d07667843b45b208f9aad
+Author: Carl Meyer <carl at oddbird.net>
+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.
+
+Index: python-django-1.7.7/django/contrib/sessions/backends/cache.py
+===================================================================
+--- python-django-1.7.7.orig/django/contrib/sessions/backends/cache.py
++++ python-django-1.7.7/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:
+Index: python-django-1.7.7/django/contrib/sessions/backends/cached_db.py
+===================================================================
+--- python-django-1.7.7.orig/django/contrib/sessions/backends/cached_db.py
++++ python-django-1.7.7/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)
+
+Index: python-django-1.7.7/django/contrib/sessions/backends/db.py
+===================================================================
+--- python-django-1.7.7.orig/django/contrib/sessions/backends/db.py
++++ python-django-1.7.7/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)),
+Index: python-django-1.7.7/django/contrib/sessions/backends/file.py
+===================================================================
+--- python-django-1.7.7.orig/django/contrib/sessions/backends/file.py
++++ python-django-1.7.7/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)
+Index: python-django-1.7.7/django/contrib/sessions/tests.py
+===================================================================
+--- python-django-1.7.7.orig/django/contrib/sessions/tests.py
++++ python-django-1.7.7/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):
+
--
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