diff -Nru python-django-1.5.4/debian/changelog python-django-1.5.4/debian/changelog --- python-django-1.5.4/debian/changelog 2014-04-24 08:16:35.000000000 +0000 +++ python-django-1.5.4/debian/changelog 2014-05-15 08:03:49.000000000 +0000 @@ -1,8 +1,26 @@ -python-django (1.5.4-1ubuntu1.2~cloud0) precise-havana; urgency=medium +python-django (1.5.4-1ubuntu1.3~cloud0) precise-havana; urgency=medium * New update for the Ubuntu Cloud Archive. - -- Openstack Ubuntu Testing Bot Thu, 24 Apr 2014 04:16:35 -0400 + -- Openstack Ubuntu Testing Bot Thu, 15 May 2014 04:03:49 -0400 + +python-django (1.5.4-1ubuntu1.3) saucy-security; urgency=medium + + * SECURITY UPDATE: cache coherency problems in old Internet Explorer + compatibility functions lead to loss of privacy and cache poisoning + attacks. (LP: #1317663) + - debian/patches/drop_fix_ie_for_vary_1_5.diff: remove fix_IE_for_vary() + and fix_IE_for_attach() functions so Cache-Control and Vary headers are + no longer modified. This may introduce some regressions for IE 6 and IE 7 + users. Patch from upstream. + - CVE-2014-1418 + * SECURITY UPDATE: The validation for redirects did not correctly validate + some malformed URLs, which are accepted by some browsers. This allows a + user to be redirected to an unsafe URL unexpectedly. + - debian/patches/is_safe_url_1_5.diff: Forbid URLs starting with '///', + forbid URLs without a host but with a path. Patch from upstream. + + -- Seth Arnold Wed, 14 May 2014 11:00:30 -0700 python-django (1.5.4-1ubuntu1.2) saucy-security; urgency=medium diff -Nru python-django-1.5.4/debian/patches/drop_fix_ie_for_vary_1_5.diff python-django-1.5.4/debian/patches/drop_fix_ie_for_vary_1_5.diff --- python-django-1.5.4/debian/patches/drop_fix_ie_for_vary_1_5.diff 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.5.4/debian/patches/drop_fix_ie_for_vary_1_5.diff 2014-05-14 19:13:25.000000000 +0000 @@ -0,0 +1,147 @@ +Description: Drop IE6/IE7 compatability functions that introduced cache + coherency errors. Fixes CVE-2014-1418. + +Origin: Original patch from upstream + +diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py +index 7fff71a..4819ad3 100644 +--- a/django/core/handlers/base.py ++++ b/django/core/handlers/base.py +@@ -22,8 +22,6 @@ class BaseHandler(object): + response_fixes = [ + http.fix_location_header, + http.conditional_content_removal, +- http.fix_IE_for_attach, +- http.fix_IE_for_vary, + ] + + def __init__(self): +diff --git a/django/http/__init__.py b/django/http/__init__.py +index 46afa34..08d9d23 100644 +--- a/django/http/__init__.py ++++ b/django/http/__init__.py +@@ -6,5 +6,4 @@ from django.http.response import (HttpResponse, StreamingHttpResponse, + HttpResponseRedirect, HttpResponseNotModified, HttpResponseBadRequest, + HttpResponseForbidden, HttpResponseNotFound, HttpResponseNotAllowed, + HttpResponseGone, HttpResponseServerError, Http404, BadHeaderError) +-from django.http.utils import (fix_location_header, conditional_content_removal, +- fix_IE_for_attach, fix_IE_for_vary) ++from django.http.utils import fix_location_header, conditional_content_removal +diff --git a/django/http/utils.py b/django/http/utils.py +index fcb3fec..ace35eb 100644 +--- a/django/http/utils.py ++++ b/django/http/utils.py +@@ -39,58 +39,3 @@ def conditional_content_removal(request, response): + else: + response.content = '' + return response +- +- +-def fix_IE_for_attach(request, response): +- """ +- This function will prevent Django from serving a Content-Disposition header +- while expecting the browser to cache it (only when the browser is IE). This +- leads to IE not allowing the client to download. +- """ +- useragent = request.META.get('HTTP_USER_AGENT', '').upper() +- if 'MSIE' not in useragent and 'CHROMEFRAME' not in useragent: +- return response +- +- offending_headers = ('no-cache', 'no-store') +- if response.has_header('Content-Disposition'): +- try: +- del response['Pragma'] +- except KeyError: +- pass +- if response.has_header('Cache-Control'): +- cache_control_values = [value.strip() for value in +- response['Cache-Control'].split(',') +- if value.strip().lower() not in offending_headers] +- +- if not len(cache_control_values): +- del response['Cache-Control'] +- else: +- response['Cache-Control'] = ', '.join(cache_control_values) +- +- return response +- +- +-def fix_IE_for_vary(request, response): +- """ +- This function will fix the bug reported at +- http://support.microsoft.com/kb/824847/en-us?spid=8722&sid=global +- by clearing the Vary header whenever the mime-type is not safe +- enough for Internet Explorer to handle. Poor thing. +- """ +- useragent = request.META.get('HTTP_USER_AGENT', '').upper() +- if 'MSIE' not in useragent and 'CHROMEFRAME' not in useragent: +- return response +- +- # These mime-types that are decreed "Vary-safe" for IE: +- safe_mime_types = ('text/html', 'text/plain', 'text/sgml') +- +- # The first part of the Content-Type field will be the MIME type, +- # everything after ';', such as character-set, can be ignored. +- mime_type = response.get('Content-Type', '').partition(';')[0] +- if mime_type not in safe_mime_types: +- try: +- del response['Vary'] +- except KeyError: +- pass +- +- return response +diff --git a/tests/regressiontests/utils/http.py b/tests/regressiontests/utils/http.py +index 6d3bc02..03706ca 100644 +--- a/tests/regressiontests/utils/http.py ++++ b/tests/regressiontests/utils/http.py +@@ -67,50 +67,6 @@ class TestUtilsHttp(unittest.TestCase): + ] + self.assertTrue(result in acceptable_results) + +- def test_fix_IE_for_vary(self): +- """ +- Regression for #16632. +- +- `fix_IE_for_vary` shouldn't crash when there's no Content-Type header. +- """ +- +- # functions to generate responses +- def response_with_unsafe_content_type(): +- r = HttpResponse(content_type="text/unsafe") +- r['Vary'] = 'Cookie' +- return r +- +- def no_content_response_with_unsafe_content_type(): +- # 'Content-Type' always defaulted, so delete it +- r = response_with_unsafe_content_type() +- del r['Content-Type'] +- return r +- +- # request with & without IE user agent +- rf = RequestFactory() +- request = rf.get('/') +- ie_request = rf.get('/', HTTP_USER_AGENT='MSIE') +- +- # not IE, unsafe_content_type +- response = response_with_unsafe_content_type() +- utils.fix_IE_for_vary(request, response) +- self.assertTrue('Vary' in response) +- +- # IE, unsafe_content_type +- response = response_with_unsafe_content_type() +- utils.fix_IE_for_vary(ie_request, response) +- self.assertFalse('Vary' in response) +- +- # not IE, no_content +- response = no_content_response_with_unsafe_content_type() +- utils.fix_IE_for_vary(request, response) +- self.assertTrue('Vary' in response) +- +- # IE, no_content +- response = no_content_response_with_unsafe_content_type() +- utils.fix_IE_for_vary(ie_request, response) +- self.assertFalse('Vary' in response) +- + def test_base36(self): + # reciprocity works + for n in [0, 1, 1000, 1000000]: diff -Nru python-django-1.5.4/debian/patches/is_safe_url_1_5.diff python-django-1.5.4/debian/patches/is_safe_url_1_5.diff --- python-django-1.5.4/debian/patches/is_safe_url_1_5.diff 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.5.4/debian/patches/is_safe_url_1_5.diff 2014-05-14 19:15:01.000000000 +0000 @@ -0,0 +1,122 @@ +Description: Blacklist several more URL patterns that can cause Chrome to + access unexpected resources. + +Origin: Original patch from upstream + +--- + django/contrib/auth/tests/views.py | 12 ++++++++---- + django/utils/http.py | 12 ++++++++++++ + tests/regressiontests/utils/http.py | 29 +++++++++++++++++++++++++++++ + 3 files changed, 49 insertions(+), 4 deletions(-) + +Index: b/django/contrib/auth/tests/views.py +=================================================================== +--- a/django/contrib/auth/tests/views.py ++++ b/django/contrib/auth/tests/views.py +@@ -326,8 +326,10 @@ + + # Those URLs should not pass the security check + for bad_url in ('http://example.com', ++ 'http:///example.com', + 'https://example.com', + 'ftp://exampel.com', ++ '///example.com', + '//example.com', + 'javascript:alert("XSS")'): + +@@ -349,8 +351,8 @@ + '/view/?param=https://example.com', + '/view?param=ftp://exampel.com', + 'view/?param=//example.com', +- 'https:///', +- 'HTTPS:///', ++ 'https://testserver/', ++ 'HTTPS://testserver/', + '//testserver/', + '/url%20with%20spaces/'): # see ticket #12534 + safe_url = '%(url)s?%(next)s=%(good_url)s' % { +@@ -522,8 +524,10 @@ + + # Those URLs should not pass the security check + for bad_url in ('http://example.com', ++ 'http:///example.com', + 'https://example.com', + 'ftp://exampel.com', ++ '///example.com', + '//example.com', + 'javascript:alert("XSS")'): + nasty_url = '%(url)s?%(next)s=%(bad_url)s' % { +@@ -543,8 +547,8 @@ + '/view/?param=https://example.com', + '/view?param=ftp://exampel.com', + 'view/?param=//example.com', +- 'https:///', +- 'HTTPS:///', ++ 'https://testserver/', ++ 'HTTPS://testserver/', + '//testserver/', + '/url%20with%20spaces/'): # see ticket #12534 + safe_url = '%(url)s?%(next)s=%(good_url)s' % { +Index: b/django/utils/http.py +=================================================================== +--- a/django/utils/http.py ++++ b/django/utils/http.py +@@ -237,6 +237,18 @@ + """ + if not url: + return False ++ # Chrome treats \ completely as / ++ url = url.replace('\\', '/') ++ # Chrome considers any URL with more than two slashes to be absolute, but ++ # urlaprse is not so flexible. Treat any url with three slashes as unsafe. ++ if url.startswith('///'): ++ return False + url_info = urllib_parse.urlparse(url) ++ # Forbid URLs like http:///example.com - with a scheme, but without a hostname. ++ # In that URL, example.com is not the hostname but, a path component. However, ++ # Chrome will still consider example.com to be the hostname, so we must not ++ # allow this syntax. ++ if not url_info.netloc and url_info.scheme: ++ return False + return (not url_info.netloc or url_info.netloc == host) and \ + (not url_info.scheme or url_info.scheme in ['http', 'https']) +Index: b/tests/regressiontests/utils/http.py +=================================================================== +--- a/tests/regressiontests/utils/http.py ++++ b/tests/regressiontests/utils/http.py +@@ -91,6 +91,35 @@ + self.assertEqual(http.int_to_base36(n), b36) + self.assertEqual(http.base36_to_int(b36), n) + ++ def test_is_safe_url(self): ++ for bad_url in ('http://example.com', ++ 'http:///example.com', ++ 'https://example.com', ++ 'ftp://exampel.com', ++ r'\\example.com', ++ r'\\\example.com', ++ r'/\\/example.com', ++ r'\\\example.com', ++ r'\\example.com', ++ r'\\//example.com', ++ r'/\/example.com', ++ r'\/example.com', ++ r'/\example.com', ++ 'http:///example.com', ++ 'http:/\//example.com', ++ 'http:\/example.com', ++ 'http:/\example.com', ++ 'javascript:alert("XSS")'): ++ self.assertFalse(http.is_safe_url(bad_url, host='testserver'), "%s should be blocked" % bad_url) ++ for good_url in ('/view/?param=http://example.com', ++ '/view/?param=https://example.com', ++ '/view?param=ftp://exampel.com', ++ 'view/?param=//example.com', ++ 'https://testserver/', ++ 'HTTPS://testserver/', ++ '//testserver/', ++ '/url%20with%20spaces/'): ++ self.assertTrue(http.is_safe_url(good_url, host='testserver'), "%s should be allowed" % good_url) + + class ETagProcessingTests(unittest.TestCase): + def testParsing(self): diff -Nru python-django-1.5.4/debian/patches/series python-django-1.5.4/debian/patches/series --- python-django-1.5.4/debian/patches/series 2014-04-23 03:11:13.000000000 +0000 +++ python-django-1.5.4/debian/patches/series 2014-05-14 18:01:22.000000000 +0000 @@ -6,3 +6,5 @@ CVE-2014-0473.patch CVE-2014-0474.patch CVE-2014-0472-regression.patch +drop_fix_ie_for_vary_1_5.diff +is_safe_url_1_5.diff