From: Chris Lamb <lamby@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".
---
 django/views/debug.py                    | 20 +++++++++-----------
 tests/view_tests/tests/py3_test_debug.py | 13 +++++++------
 2 files changed, 16 insertions(+), 17 deletions(-)

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)
