Fixed #25099 -- Fixed crash in AdminEmailHandler on DisallowedHost.

This commit is contained in:
Vlastimil Zíma 2015-09-03 20:52:49 +02:00 committed by Tim Graham
parent 4c0447b2ae
commit cf29b6b561
5 changed files with 71 additions and 5 deletions

View File

@ -68,8 +68,11 @@ class HttpRequest(object):
'<%s: %s %r>' % (self.__class__.__name__, self.method, force_str(self.get_full_path())) '<%s: %s %r>' % (self.__class__.__name__, self.method, force_str(self.get_full_path()))
) )
def get_host(self): def _get_raw_host(self):
"""Returns the HTTP host using the environment or request headers.""" """
Return the HTTP host using the environment or request headers. Skip
allowed hosts protection, so may return an insecure host.
"""
# We try three options, in order of decreasing preference. # We try three options, in order of decreasing preference.
if settings.USE_X_FORWARDED_HOST and ( if settings.USE_X_FORWARDED_HOST and (
'HTTP_X_FORWARDED_HOST' in self.META): 'HTTP_X_FORWARDED_HOST' in self.META):
@ -82,6 +85,11 @@ class HttpRequest(object):
server_port = self.get_port() server_port = self.get_port()
if server_port != ('443' if self.is_secure() else '80'): if server_port != ('443' if self.is_secure() else '80'):
host = '%s:%s' % (host, server_port) host = '%s:%s' % (host, server_port)
return host
def get_host(self):
"""Return the HTTP host using the environment or request headers."""
host = self._get_raw_host()
# There is no hostname validation when DEBUG=True # There is no hostname validation when DEBUG=True
if settings.DEBUG: if settings.DEBUG:
@ -138,6 +146,17 @@ class HttpRequest(object):
raise raise
return value return value
def get_raw_uri(self):
"""
Return an absolute URI from variables available in this request. Skip
allowed hosts protection, so may return insecure URI.
"""
return '{scheme}://{host}{path}'.format(
scheme=self.scheme,
host=self._get_raw_host(),
path=self.get_full_path(),
)
def build_absolute_uri(self, location=None): def build_absolute_uri(self, location=None):
""" """
Builds an absolute URI from the location and the variables available in Builds an absolute URI from the location and the variables available in

View File

@ -669,7 +669,7 @@ TECHNICAL_500_TEMPLATE = ("""
</tr> </tr>
<tr> <tr>
<th>Request URL:</th> <th>Request URL:</th>
<td>{{ request.build_absolute_uri|escape }}</td> <td>{{ request.get_raw_uri|escape }}</td>
</tr> </tr>
{% endif %} {% endif %}
<tr> <tr>
@ -849,7 +849,7 @@ Environment:
{% if request %} {% if request %}
Request Method: {{ request.META.REQUEST_METHOD }} Request Method: {{ request.META.REQUEST_METHOD }}
Request URL: {{ request.build_absolute_uri|escape }} Request URL: {{ request.get_raw_uri|escape }}
{% endif %} {% endif %}
Django Version: {{ django_version_info }} Django Version: {{ django_version_info }}
Python Version: {{ sys_version_info }} Python Version: {{ sys_version_info }}
@ -1048,7 +1048,7 @@ TECHNICAL_500_TEXT_TEMPLATE = ("""{% firstof exception_type 'Report' %}{% if req
{% firstof exception_value 'No exception message supplied' %} {% firstof exception_value 'No exception message supplied' %}
{% if request %} {% if request %}
Request Method: {{ request.META.REQUEST_METHOD }} Request Method: {{ request.META.REQUEST_METHOD }}
Request URL: {{ request.build_absolute_uri }}{% endif %} Request URL: {{ request.get_raw_uri }}{% endif %}
Django Version: {{ django_version_info }} Django Version: {{ django_version_info }}
Python Executable: {{ sys_executable }} Python Executable: {{ sys_executable }}
Python Version: {{ sys_version_info }} Python Version: {{ sys_version_info }}

View File

@ -352,6 +352,25 @@ class AdminEmailHandlerTest(SimpleTestCase):
self.assertEqual(len(mail.outbox), 1) self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].to, ['manager@example.com']) self.assertEqual(mail.outbox[0].to, ['manager@example.com'])
@override_settings(ALLOWED_HOSTS='example.com')
def test_disallowed_host_doesnt_crash(self):
admin_email_handler = self.get_admin_email_handler(self.logger)
old_include_html = admin_email_handler.include_html
# Text email
admin_email_handler.include_html = False
try:
self.client.get('/', HTTP_HOST='evil.com')
finally:
admin_email_handler.include_html = old_include_html
# HTML email
admin_email_handler.include_html = True
try:
self.client.get('/', HTTP_HOST='evil.com')
finally:
admin_email_handler.include_html = old_include_html
class SettingsConfigTest(AdminScriptTestCase): class SettingsConfigTest(AdminScriptTestCase):
""" """

View File

@ -502,6 +502,18 @@ class RequestsTests(SimpleTestCase):
with self.assertRaises(UnreadablePostError): with self.assertRaises(UnreadablePostError):
request.FILES request.FILES
@override_settings(ALLOWED_HOSTS=['example.com'])
def test_get_raw_uri(self):
factory = RequestFactory(HTTP_HOST='evil.com')
request = factory.get('////absolute-uri')
self.assertEqual(request.get_raw_uri(), 'http://evil.com//absolute-uri')
request = factory.get('/?foo=bar')
self.assertEqual(request.get_raw_uri(), 'http://evil.com/?foo=bar')
request = factory.get('/path/with:colons')
self.assertEqual(request.get_raw_uri(), 'http://evil.com/path/with:colons')
class HostValidationTests(SimpleTestCase): class HostValidationTests(SimpleTestCase):
poisoned_hosts = [ poisoned_hosts = [

View File

@ -439,6 +439,14 @@ class ExceptionReporterTests(SimpleTestCase):
"Evaluation exception reason not mentioned in traceback" "Evaluation exception reason not mentioned in traceback"
) )
@override_settings(ALLOWED_HOSTS='example.com')
def test_disallowed_host(self):
"An exception report can be generated even for a disallowed host."
request = self.rf.get('/', HTTP_HOST='evil.com')
reporter = ExceptionReporter(request, None, None, None)
html = reporter.get_traceback_html()
self.assertIn("http://evil.com/", html)
class PlainTextReportTests(SimpleTestCase): class PlainTextReportTests(SimpleTestCase):
rf = RequestFactory() rf = RequestFactory()
@ -495,6 +503,14 @@ class PlainTextReportTests(SimpleTestCase):
reporter = ExceptionReporter(None, None, "I'm a little teapot", None) reporter = ExceptionReporter(None, None, "I'm a little teapot", None)
reporter.get_traceback_text() reporter.get_traceback_text()
@override_settings(ALLOWED_HOSTS='example.com')
def test_disallowed_host(self):
"An exception report can be generated even for a disallowed host."
request = self.rf.get('/', HTTP_HOST='evil.com')
reporter = ExceptionReporter(request, None, None, None)
text = reporter.get_traceback_text()
self.assertIn("http://evil.com/", text)
class ExceptionReportTestMixin(object): class ExceptionReportTestMixin(object):