[adding 874...@bugs.debian.org to CC]

Hi Salvatore,

> > > There is as well a no-dsa tagged entry (CVE-2017-12794), which is only
> > > relevant when "DEBUG = true". But as we do an update now via a DSA, we
> > > can include this fix as well.
> > 
> > That makes sense. Shall I go ahead and add this CVE-2017-12794 and send
> > another debdiff?
> 
> Yes please.

Full diff attached. Please let me know if this is okay to upload.

  Source: python-django
  Version: 1:1.10.7-2+deb9u2
  Distribution: stretch-security
  Urgency: high
  Maintainer: Chris Lamb <la...@debian.org>
  Timestamp: 1533177448
  Date: Thu, 02 Aug 2018 10:37:28 +0800
  Closes: 874415 905216
  Changes:
   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)
     * CVE-2017-12794: Fix a cross-site scripting attack in the technical HTTP 
500
       page. This vulnerability did not affect production sites as they 
typically
       do not run with "DEBUG = True". (Closes: #874415)


Regards,

-- 
      ,''`.
     : :'  :     Chris Lamb
     `. `'`      la...@debian.org / chris-lamb.co.uk
       `-
diff --git a/debian/changelog b/debian/changelog
index 472d500fb..e77a81a21 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,18 @@
+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)
+  * CVE-2017-12794: Fix a cross-site scripting attack in the technical HTTP 500
+    page. This vulnerability did not affect production sites as they typically
+    do not run with "DEBUG = True". (Closes: #874415)
+
+ -- Chris Lamb <la...@debian.org>  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 <la...@debian.org>
+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.
+
+ -- <https://www.djangoproject.com/weblog/2018/aug/01/security-releases/>
+
+Backported by Chris Lamb <la...@debian.org> 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/0016-CVE-2017-12794.patch 
b/debian/patches/0016-CVE-2017-12794.patch
new file mode 100644
index 000000000..d472183db
--- /dev/null
+++ b/debian/patches/0016-CVE-2017-12794.patch
@@ -0,0 +1,144 @@
+From: Chris Lamb <la...@debian.org>
+Date: Fri, 3 Aug 2018 11:48:27 +0800
+Subject: CVE-2017-12794
+
+Fix a cross-site scripting attack in the technical HTTP 500
+page. This vulnerability did not affect production sites as they
+typically do not run with "DEBUG = True".
+---
+ debian/changelog                         |  3 +++
+ django/views/debug.py                    | 20 +++++++++-----------
+ tests/view_tests/tests/py3_test_debug.py | 13 +++++++------
+ 3 files changed, 19 insertions(+), 17 deletions(-)
+
+diff --git a/debian/changelog b/debian/changelog
+index ace826d..43cc398 100644
+--- a/debian/changelog
++++ b/debian/changelog
+@@ -7,6 +7,9 @@ python-django (1:1.10.7-2+deb9u2) stretch-security; 
urgency=high
+     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)
++  * CVE-2017-12794: Fix a cross-site scripting attack in the technical HTTP 
500
++    debug page. This vulnerability did not affect production sites as they
++    typically do not run with "DEBUG = True". (Closes: #874415)
+ 
+  -- Chris Lamb <la...@debian.org>  Thu, 02 Aug 2018 10:37:28 +0800
+ 
+diff --git a/django/views/debug.py b/django/views/debug.py
+index 0ed55fd..327ff43 100644
+--- a/django/views/debug.py
++++ b/django/views/debug.py
+@@ -775,38 +775,37 @@ TECHNICAL_500_TEMPLATE = ("""
+   <h2>Traceback <span class="commands">{% if not is_email %}<a href="#" 
onclick="return switchPastebinFriendly(this);">
+     Switch to copy-and-paste view</a></span>{% endif %}
+   </h2>
+-  {% autoescape off %}
+   <div id="browserTraceback">
+     <ul class="traceback">
+       {% for frame in frames %}
+         {% ifchanged frame.exc_cause %}{% if frame.exc_cause %}
+           <li><h3>
+           {% if frame.exc_cause_explicit %}
+-            The above exception ({{ frame.exc_cause }}) was the direct cause 
of the following exception:
++            The above exception ({{ frame.exc_cause|force_escape }}) was the 
direct cause of the following exception:
+           {% else %}
+-            During handling of the above exception ({{ frame.exc_cause }}), 
another exception occurred:
++            During handling of the above exception ({{ 
frame.exc_cause|force_escape }}), another exception occurred:
+           {% endif %}
+         </h3></li>
+         {% endif %}{% endifchanged %}
+         <li class="frame {{ frame.type }}">
+-          <code>{{ frame.filename|escape }}</code> in <code>{{ 
frame.function|escape }}</code>
++          <code>{{ frame.filename }}</code> in <code>{{ frame.function 
}}</code>
+ 
+           {% if frame.context_line %}
+             <div class="context" id="c{{ frame.id }}">
+               {% if frame.pre_context and not is_email %}
+                 <ol start="{{ frame.pre_context_lineno }}" 
class="pre-context" id="pre{{ frame.id }}">
+                 {% for line in frame.pre_context %}
+-                  <li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id 
}}')"><pre>{{ line|escape }}</pre></li>
++                  <li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id 
}}')"><pre>{{ line }}</pre></li>
+                 {% endfor %}
+                 </ol>
+               {% endif %}
+               <ol start="{{ frame.lineno }}" class="context-line">
+                 <li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id 
}}')"><pre>
+-"""            """{{ frame.context_line|escape }}</pre>{% if not is_email %} 
<span>...</span>{% endif %}</li></ol>
++"""            """{{ frame.context_line }}</pre>{% if not is_email %} 
<span>...</span>{% endif %}</li></ol>
+               {% if frame.post_context and not is_email  %}
+                 <ol start='{{ frame.lineno|add:"1" }}' class="post-context" 
id="post{{ frame.id }}">
+                   {% for line in frame.post_context %}
+-                  <li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id 
}}')"><pre>{{ line|escape }}</pre></li>
++                  <li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id 
}}')"><pre>{{ line }}</pre></li>
+                   {% endfor %}
+               </ol>
+               {% endif %}
+@@ -831,7 +830,7 @@ TECHNICAL_500_TEMPLATE = ("""
+               <tbody>
+                 {% for var in frame.vars|dictsort:0 %}
+                   <tr>
+-                    <td>{{ var.0|force_escape }}</td>
++                    <td>{{ var.0 }}</td>
+                     <td class="code"><pre>{{ var.1 }}</pre></td>
+                   </tr>
+                 {% endfor %}
+@@ -842,7 +841,6 @@ TECHNICAL_500_TEMPLATE = ("""
+       {% endfor %}
+     </ul>
+   </div>
+-  {% endautoescape %}
+   <form action="http://dpaste.com/"; name="pasteform" id="pasteform" 
method="post">
+ {% if not is_email %}
+   <div id="pastebinTraceback" class="pastebin">
+@@ -888,9 +886,9 @@ In template {{ template_info.name }}, error at line {{ 
template_info.line }}
+ 
+ Traceback:{% for frame in frames %}
+ {% ifchanged frame.exc_cause %}{% if frame.exc_cause %}{% if 
frame.exc_cause_explicit %}
+-The above exception ({{ frame.exc_cause }}) was the direct cause of the 
following exception:
++The above exception ({{ frame.exc_cause|force_escape }}) was the direct cause 
of the following exception:
+ {% else %}
+-During handling of the above exception ({{ frame.exc_cause }}), another 
exception occurred:
++During handling of the above exception ({{ frame.exc_cause|force_escape }}), 
another exception occurred:
+ {% endif %}{% endif %}{% endifchanged %}
+ File "{{ frame.filename|escape }}" in {{ frame.function|escape }}
+ {% if frame.context_line %}  {{ frame.lineno }}. {{ frame.context_line|escape 
}}{% endif %}{% endfor %}
+diff --git a/tests/view_tests/tests/py3_test_debug.py 
b/tests/view_tests/tests/py3_test_debug.py
+index 30201ba..316179a 100644
+--- a/tests/view_tests/tests/py3_test_debug.py
++++ b/tests/view_tests/tests/py3_test_debug.py
+@@ -9,6 +9,7 @@ error (raise ... from ...) can't be silenced using NOQA.
+ import sys
+ 
+ from django.test import RequestFactory, TestCase
++from django.utils.safestring import mark_safe
+ from django.views.debug import ExceptionReporter
+ 
+ 
+@@ -20,10 +21,10 @@ class Py3ExceptionReporterTests(TestCase):
+         request = self.rf.get('/test_view/')
+         try:
+             try:
+-                raise AttributeError('Top level')
++                raise AttributeError(mark_safe('<p>Top level</p>'))
+             except AttributeError as explicit:
+                 try:
+-                    raise ValueError('Second exception') from explicit
++                    raise ValueError('<p>Second exception</p>') from explicit
+                 except ValueError:
+                     raise IndexError('Final exception')
+         except Exception:
+@@ -37,9 +38,9 @@ class Py3ExceptionReporterTests(TestCase):
+         html = reporter.get_traceback_html()
+         # Both messages are twice on page -- one rendered as html,
+         # one as plain text (for pastebin)
+-        self.assertEqual(2, html.count(explicit_exc.format("Top level")))
+-        self.assertEqual(2, html.count(implicit_exc.format("Second 
exception")))
++        self.assertEqual(2, html.count(explicit_exc.format('&lt;p&gt;Top 
level&lt;/p&gt;')))
++        self.assertEqual(2, html.count(implicit_exc.format('&lt;p&gt;Second 
exception&lt;/p&gt;')))
+ 
+         text = reporter.get_traceback_text()
+-        self.assertIn(explicit_exc.format("Top level"), text)
+-        self.assertIn(implicit_exc.format("Second exception"), text)
++        self.assertIn(explicit_exc.format('<p>Top level</p>'), text)
++        self.assertIn(implicit_exc.format('<p>Second exception</p>'), text)
diff --git a/debian/patches/series b/debian/patches/series
index 13daeb8c4..0b2048f8f 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -5,3 +5,5 @@ 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
+0016-CVE-2017-12794.patch

Reply via email to