Fixed CVE-2017-12794 -- Fixed XSS possibility in traceback section of technical 500 debug page.

This is a security fix.
This commit is contained in:
Tim Graham 2017-08-09 21:12:37 -04:00
parent 73b6d02747
commit 46e2b9e059
5 changed files with 35 additions and 19 deletions

View File

@ -8,7 +8,7 @@ from pathlib import Path
from django.conf import settings from django.conf import settings
from django.http import HttpResponse, HttpResponseNotFound from django.http import HttpResponse, HttpResponseNotFound
from django.template import Context, Engine, TemplateDoesNotExist from django.template import Context, Engine, TemplateDoesNotExist
from django.template.defaultfilters import force_escape, pprint from django.template.defaultfilters import pprint
from django.urls import Resolver404, resolve from django.urls import Resolver404, resolve
from django.utils import timezone from django.utils import timezone
from django.utils.datastructures import MultiValueDict from django.utils.datastructures import MultiValueDict
@ -271,7 +271,7 @@ class ExceptionReporter:
# Trim large blobs of data # Trim large blobs of data
if len(v) > 4096: if len(v) > 4096:
v = '%s... <trimmed %d bytes string>' % (v[0:4096], len(v)) v = '%s... <trimmed %d bytes string>' % (v[0:4096], len(v))
frame_vars.append((k, force_escape(v))) frame_vars.append((k, v))
frame['vars'] = frame_vars frame['vars'] = frame_vars
frames[i] = frame frames[i] = frame

View File

@ -212,38 +212,37 @@
<h2>Traceback <span class="commands">{% if not is_email %}<a href="#" onclick="return switchPastebinFriendly(this);"> <h2>Traceback <span class="commands">{% if not is_email %}<a href="#" onclick="return switchPastebinFriendly(this);">
Switch to copy-and-paste view</a></span>{% endif %} Switch to copy-and-paste view</a></span>{% endif %}
</h2> </h2>
{% autoescape off %}
<div id="browserTraceback"> <div id="browserTraceback">
<ul class="traceback"> <ul class="traceback">
{% for frame in frames %} {% for frame in frames %}
{% ifchanged frame.exc_cause %}{% if frame.exc_cause %} {% ifchanged frame.exc_cause %}{% if frame.exc_cause %}
<li><h3> <li><h3>
{% if frame.exc_cause_explicit %} {% 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 %} {% 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 %}
</h3></li> </h3></li>
{% endif %}{% endifchanged %} {% endif %}{% endifchanged %}
<li class="frame {{ frame.type }}"> <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 %} {% if frame.context_line %}
<div class="context" id="c{{ frame.id }}"> <div class="context" id="c{{ frame.id }}">
{% if frame.pre_context and not is_email %} {% if frame.pre_context and not is_email %}
<ol start="{{ frame.pre_context_lineno }}" class="pre-context" id="pre{{ frame.id }}"> <ol start="{{ frame.pre_context_lineno }}" class="pre-context" id="pre{{ frame.id }}">
{% for line in frame.pre_context %} {% 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 %} {% endfor %}
</ol> </ol>
{% endif %} {% endif %}
<ol start="{{ frame.lineno }}" class="context-line"> <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> <li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')"><pre>{{ frame.context_line }}</pre>{% if not is_email %} <span>...</span>{% endif %}</li>
</ol> </ol>
{% if frame.post_context and not is_email %} {% if frame.post_context and not is_email %}
<ol start='{{ frame.lineno|add:"1" }}' class="post-context" id="post{{ frame.id }}"> <ol start='{{ frame.lineno|add:"1" }}' class="post-context" id="post{{ frame.id }}">
{% for line in frame.post_context %} {% 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 %} {% endfor %}
</ol> </ol>
{% endif %} {% endif %}
@ -268,7 +267,7 @@
<tbody> <tbody>
{% for var in frame.vars|dictsort:0 %} {% for var in frame.vars|dictsort:0 %}
<tr> <tr>
<td>{{ var.0|force_escape }}</td> <td>{{ var.0 }}</td>
<td class="code"><pre>{{ var.1 }}</pre></td> <td class="code"><pre>{{ var.1 }}</pre></td>
</tr> </tr>
{% endfor %} {% endfor %}
@ -279,7 +278,6 @@
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
{% endautoescape %}
<form action="http://dpaste.com/" name="pasteform" id="pasteform" method="post"> <form action="http://dpaste.com/" name="pasteform" id="pasteform" method="post">
{% if not is_email %} {% if not is_email %}
<div id="pastebinTraceback" class="pastebin"> <div id="pastebinTraceback" class="pastebin">
@ -318,9 +316,9 @@ In template {{ template_info.name }}, error at line {{ template_info.line }}
Traceback:{% for frame in frames %} Traceback:{% for frame in frames %}
{% ifchanged frame.exc_cause %}{% if frame.exc_cause %}{% if frame.exc_cause_explicit %} {% 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 %} {% 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 %} {% endif %}{% endif %}{% endifchanged %}
File "{{ frame.filename }}" in {{ frame.function }} File "{{ frame.filename }}" in {{ frame.function }}
{% if frame.context_line %} {{ frame.lineno }}. {{ frame.context_line }}{% endif %}{% endfor %} {% if frame.context_line %} {{ frame.lineno }}. {{ frame.context_line }}{% endif %}{% endfor %}

View File

@ -5,3 +5,12 @@ Django 1.10.8 release notes
*September 5, 2017* *September 5, 2017*
Django 1.10.8 fixes a security issue in 1.10.7. Django 1.10.8 fixes a security issue in 1.10.7.
CVE-2017-12794: Possible XSS in traceback section of technical 500 debug page
=============================================================================
In older versions, HTML autoescaping was disabled in a portion of the template
for the technical 500 debug page. Given the right circumstances, this allowed
a cross-site scripting attack. This vulnerability shouldn't affect most
production sites since you shouldn't run with ``DEBUG = True`` (which makes
this page accessible) in your production settings.

View File

@ -6,6 +6,15 @@ Django 1.11.5 release notes
Django 1.11.5 fixes a security issue and several bugs in 1.11.4. Django 1.11.5 fixes a security issue and several bugs in 1.11.4.
CVE-2017-12794: Possible XSS in traceback section of technical 500 debug page
=============================================================================
In older versions, HTML autoescaping was disabled in a portion of the template
for the technical 500 debug page. Given the right circumstances, this allowed
a cross-site scripting attack. This vulnerability shouldn't affect most
production sites since you shouldn't run with ``DEBUG = True`` (which makes
this page accessible) in your production settings.
Bugfixes Bugfixes
======== ========

View File

@ -349,10 +349,10 @@ class ExceptionReporterTests(SimpleTestCase):
request = self.rf.get('/test_view/') request = self.rf.get('/test_view/')
try: try:
try: try:
raise AttributeError('Top level') raise AttributeError(mark_safe('<p>Top level</p>'))
except AttributeError as explicit: except AttributeError as explicit:
try: try:
raise ValueError('Second exception') from explicit raise ValueError(mark_safe('<p>Second exception</p>')) from explicit
except ValueError: except ValueError:
raise IndexError(mark_safe('<p>Final exception</p>')) raise IndexError(mark_safe('<p>Final exception</p>'))
except Exception: except Exception:
@ -366,13 +366,13 @@ class ExceptionReporterTests(SimpleTestCase):
html = reporter.get_traceback_html() html = reporter.get_traceback_html()
# Both messages are twice on page -- one rendered as html, # Both messages are twice on page -- one rendered as html,
# one as plain text (for pastebin) # one as plain text (for pastebin)
self.assertEqual(2, html.count(explicit_exc.format("Top level"))) self.assertEqual(2, html.count(explicit_exc.format('&lt;p&gt;Top level&lt;/p&gt;')))
self.assertEqual(2, html.count(implicit_exc.format("Second exception"))) self.assertEqual(2, html.count(implicit_exc.format('&lt;p&gt;Second exception&lt;/p&gt;')))
self.assertEqual(10, html.count('&lt;p&gt;Final exception&lt;/p&gt;')) self.assertEqual(10, html.count('&lt;p&gt;Final exception&lt;/p&gt;'))
text = reporter.get_traceback_text() text = reporter.get_traceback_text()
self.assertIn(explicit_exc.format("Top level"), text) self.assertIn(explicit_exc.format('<p>Top level</p>'), text)
self.assertIn(implicit_exc.format("Second exception"), text) self.assertIn(implicit_exc.format('<p>Second exception</p>'), text)
self.assertEqual(3, text.count('<p>Final exception</p>')) self.assertEqual(3, text.count('<p>Final exception</p>'))
def test_reporting_frames_without_source(self): def test_reporting_frames_without_source(self):