diff --git a/debian/changelog b/debian/changelog index 472d500fb..ace826d6c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,15 @@ +python-django (1:1.10.7-2+deb9u2) stretch-security; urgency=high + + * Non-maintainer upload by the Security Team. + * CVE-2018-14574: Fix an open redirect possibility in CommonMiddleware. + If the django.middleware.common.CommonMiddleware and the APPEND_SLASH + setting were both enabled, and if the project has a URL pattern that + accepted any path ending in a slash then a request to a maliciously crafted + URL of that site could lead to a redirect to another site, enabling + phishing and other attacks. (Closes: #905216) + + -- Chris Lamb Thu, 02 Aug 2018 10:37:28 +0800 + python-django (1:1.10.7-2+deb9u1) stretch-security; urgency=high * Non-maintainer upload by the LTS Team. diff --git a/debian/patches/0015-CVE-2018-14574.patch b/debian/patches/0015-CVE-2018-14574.patch new file mode 100644 index 000000000..c8bf439e9 --- /dev/null +++ b/debian/patches/0015-CVE-2018-14574.patch @@ -0,0 +1,153 @@ +From: Chris Lamb +Date: Thu, 2 Aug 2018 10:28:56 +0800 +Subject: CVE-2018-14574 + +Open redirect possibility in CommonMiddleware + +If the django.middleware.common.CommonMiddleware and the APPEND_SLASH setting +are both enabled, and if the project has a URL pattern that accepts any path +ending in a slash (many content management systems have such a pattern), then a +request to a maliciously crafted URL of that site could lead to a redirect to +another site, enabling phishing and other attacks. + +Thanks Andreas Hug for reporting this issue. + + -- + +Backported by Chris Lamb from: + + https://github.com/django/django/commit/d6eaee092709aad477a9894598496c6deec532ff +--- + django/middleware/common.py | 3 +++ + django/urls/resolvers.py | 8 ++++---- + django/utils/http.py | 11 +++++++++++ + tests/middleware/tests.py | 19 +++++++++++++++++++ + tests/middleware/urls.py | 2 ++ + tests/utils_tests/test_http.py | 10 ++++++++++ + 6 files changed, 49 insertions(+), 4 deletions(-) + +diff --git a/django/middleware/common.py b/django/middleware/common.py +index 4cec6f0..4ac5e01 100644 +--- a/django/middleware/common.py ++++ b/django/middleware/common.py +@@ -9,6 +9,7 @@ from django.urls import is_valid_path + from django.utils.cache import get_conditional_response, set_response_etag + from django.utils.deprecation import MiddlewareMixin + from django.utils.encoding import force_text ++from django.utils.http import escape_leading_slashes + from django.utils.http import unquote_etag + from django.utils.six.moves.urllib.parse import urlparse + +@@ -90,6 +91,8 @@ class CommonMiddleware(MiddlewareMixin): + POST, PUT, or PATCH. + """ + new_path = request.get_full_path(force_append_slash=True) ++ # Prevent construction of scheme relative urls. ++ new_path = escape_leading_slashes(new_path) + if settings.DEBUG and request.method in ('POST', 'PUT', 'PATCH'): + raise RuntimeError( + "You called this URL via %(method)s, but the URL doesn't end " +diff --git a/django/urls/resolvers.py b/django/urls/resolvers.py +index cec960d..da82d56 100644 +--- a/django/urls/resolvers.py ++++ b/django/urls/resolvers.py +@@ -18,7 +18,9 @@ from django.utils import lru_cache, six + from django.utils.datastructures import MultiValueDict + from django.utils.encoding import force_str, force_text + from django.utils.functional import cached_property +-from django.utils.http import RFC3986_SUBDELIMS, urlquote ++from django.utils.http import ( ++ RFC3986_SUBDELIMS, escape_leading_slashes, urlquote, ++) + from django.utils.regex_helper import normalize + from django.utils.translation import get_language + +@@ -373,9 +375,7 @@ class RegexURLResolver(LocaleRegexProvider): + # safe characters from `pchar` definition of RFC 3986 + url = urlquote(candidate_pat % candidate_subs, safe=RFC3986_SUBDELIMS + str('/~:@')) + # Don't allow construction of scheme relative urls. +- if url.startswith('//'): +- url = '/%%2F%s' % url[2:] +- return url ++ return escape_leading_slashes(url) + # lookup_view can be URL name or callable, but callables are not + # friendly in error messages. + m = getattr(lookup_view, '__module__', None) +diff --git a/django/utils/http.py b/django/utils/http.py +index 812ddb2..3898331 100644 +--- a/django/utils/http.py ++++ b/django/utils/http.py +@@ -437,3 +437,14 @@ def limited_parse_qsl(qs, keep_blank_values=False, encoding='utf-8', + value = unquote(nv[1].replace(b'+', b' ')) + r.append((name, value)) + return r ++ ++ ++def escape_leading_slashes(url): ++ """ ++ If redirecting to an absolute path (two leading slashes), a slash must be ++ escaped to prevent browsers from handling the path as schemaless and ++ redirecting to another host. ++ """ ++ if url.startswith('//'): ++ url = '/%2F{}'.format(url[2:]) ++ return url +diff --git a/tests/middleware/tests.py b/tests/middleware/tests.py +index f87bb9d..0120529 100644 +--- a/tests/middleware/tests.py ++++ b/tests/middleware/tests.py +@@ -122,6 +122,25 @@ class CommonMiddlewareTest(SimpleTestCase): + self.assertEqual(r.status_code, 301) + self.assertEqual(r.url, '/needsquoting%23/') + ++ @override_settings(APPEND_SLASH=True) ++ def test_append_slash_leading_slashes(self): ++ """ ++ Paths starting with two slashes are escaped to prevent open redirects. ++ If there's a URL pattern that allows paths to start with two slashes, a ++ request with path //evil.com must not redirect to //evil.com/ (appended ++ slash) which is a schemaless absolute URL. The browser would navigate ++ to evil.com/. ++ """ ++ # Use 4 slashes because of RequestFactory behavior. ++ request = self.rf.get('////evil.com/security') ++ response = HttpResponseNotFound() ++ r = CommonMiddleware().process_request(request) ++ self.assertEqual(r.status_code, 301) ++ self.assertEqual(r.url, '/%2Fevil.com/security/') ++ r = CommonMiddleware().process_response(request, response) ++ self.assertEqual(r.status_code, 301) ++ self.assertEqual(r.url, '/%2Fevil.com/security/') ++ + @override_settings(APPEND_SLASH=False, PREPEND_WWW=True) + def test_prepend_www(self): + request = self.rf.get('/path/') +diff --git a/tests/middleware/urls.py b/tests/middleware/urls.py +index 8c6621d..d623e7d 100644 +--- a/tests/middleware/urls.py ++++ b/tests/middleware/urls.py +@@ -6,4 +6,6 @@ urlpatterns = [ + url(r'^noslash$', views.empty_view), + url(r'^slash/$', views.empty_view), + url(r'^needsquoting#/$', views.empty_view), ++ # Accepts paths with two leading slashes. ++ url(r'^(.+)/security/$', views.empty_view), + ] +diff --git a/tests/utils_tests/test_http.py b/tests/utils_tests/test_http.py +index efe6b9a..ed4a099 100644 +--- a/tests/utils_tests/test_http.py ++++ b/tests/utils_tests/test_http.py +@@ -211,3 +211,13 @@ class HttpDateProcessingTests(unittest.TestCase): + def test_parsing_asctime(self): + parsed = http.parse_http_date('Sun Nov 6 08:49:37 1994') + self.assertEqual(datetime.utcfromtimestamp(parsed), datetime(1994, 11, 6, 8, 49, 37)) ++ ++ ++class EscapeLeadingSlashesTests(unittest.TestCase): ++ def test(self): ++ tests = ( ++ ('//example.com', '/%2Fexample.com'), ++ ('//', '/%2F'), ++ ) ++ for url, expected in tests: ++ self.assertEqual(http.escape_leading_slashes(url), expected) diff --git a/debian/patches/series b/debian/patches/series index 13daeb8c4..ac9c311cd 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -5,3 +5,4 @@ fix-migration-fake-initial-2.patch fix-test-middleware-classes-headers.patch 0013-CVE-2018-7536.patch 0014-CVE-2018-7537.patch +0015-CVE-2018-14574.patch