1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
|
From: Tom Hacohen <tasn@users.noreply.github.com>
Date: Fri, 4 Jan 2019 02:21:55 +0000
Subject: Fixed #30070,
CVE-2019-3498 -- Fixed content spoofing possiblity in the default 404 page.
Co-Authored-By: Tim Graham <timograham@gmail.com>
Backport of 1ecc0a395be721e987e8e9fdfadde952b6dee1c7 from master.
---
django/views/defaults.py | 8 +++++---
tests/handlers/tests.py | 12 ++++++++----
2 files changed, 13 insertions(+), 7 deletions(-)
diff --git a/django/views/defaults.py b/django/views/defaults.py
index 348837e..5ec9ac8 100644
--- a/django/views/defaults.py
+++ b/django/views/defaults.py
@@ -2,6 +2,7 @@ from django import http
from django.template import Context, Engine, TemplateDoesNotExist, loader
from django.utils import six
from django.utils.encoding import force_text
+from django.utils.http import urlquote
from django.views.decorators.csrf import requires_csrf_token
ERROR_404_TEMPLATE_NAME = '404.html'
@@ -21,7 +22,8 @@ def page_not_found(request, exception, template_name=ERROR_404_TEMPLATE_NAME):
Templates: :template:`404.html`
Context:
request_path
- The path of the requested URL (e.g., '/app/pages/bad_page/')
+ The path of the requested URL (e.g., '/app/pages/bad_page/'). It's
+ quoted to prevent a content injection attack.
exception
The message from the exception which triggered the 404 (if one was
supplied), or the exception class name
@@ -37,7 +39,7 @@ def page_not_found(request, exception, template_name=ERROR_404_TEMPLATE_NAME):
if isinstance(message, six.text_type):
exception_repr = message
context = {
- 'request_path': request.path,
+ 'request_path': urlquote(request.path),
'exception': exception_repr,
}
try:
@@ -50,7 +52,7 @@ def page_not_found(request, exception, template_name=ERROR_404_TEMPLATE_NAME):
raise
template = Engine().from_string(
'<h1>Not Found</h1>'
- '<p>The requested URL {{ request_path }} was not found on this server.</p>')
+ '<p>The requested resource was not found on this server.</p>')
body = template.render(Context(context))
content_type = 'text/html'
return http.HttpResponseNotFound(body, content_type=content_type)
diff --git a/tests/handlers/tests.py b/tests/handlers/tests.py
index 9f01cb2..50a3488 100644
--- a/tests/handlers/tests.py
+++ b/tests/handlers/tests.py
@@ -2,6 +2,7 @@
from __future__ import unicode_literals
+import sys
import unittest
from django.core.exceptions import ImproperlyConfigured
@@ -19,6 +20,8 @@ try:
except ImportError: # Python < 3.5
HTTPStatus = None
+PY37 = sys.version_info >= (3, 7, 0)
+
class HandlerTests(SimpleTestCase):
@@ -180,16 +183,17 @@ class HandlerRequestTests(SimpleTestCase):
def test_invalid_urls(self):
response = self.client.get('~%A9helloworld')
- self.assertContains(response, '~%A9helloworld', status_code=404)
+ self.assertEqual(response.status_code, 404)
+ self.assertEqual(response.context['request_path'], '/~%25A9helloworld' if PY37 else '/%7E%25A9helloworld')
response = self.client.get('d%aao%aaw%aan%aal%aao%aaa%aad%aa/')
- self.assertContains(response, 'd%AAo%AAw%AAn%AAl%AAo%AAa%AAd%AA', status_code=404)
+ self.assertEqual(response.context['request_path'], '/d%25AAo%25AAw%25AAn%25AAl%25AAo%25AAa%25AAd%25AA')
response = self.client.get('/%E2%99%E2%99%A5/')
- self.assertContains(response, '%E2%99\u2665', status_code=404)
+ self.assertEqual(response.context['request_path'], '/%25E2%2599%E2%99%A5/')
response = self.client.get('/%E2%98%8E%E2%A9%E2%99%A5/')
- self.assertContains(response, '\u260e%E2%A9\u2665', status_code=404)
+ self.assertEqual(response.context['request_path'], '/%E2%98%8E%25E2%25A9%E2%99%A5/')
def test_environ_path_info_type(self):
environ = RequestFactory().get('/%E2%A8%87%87%A5%E2%A8%A0').environ
|