Fixed #30070, CVE-2019-3498 -- Fixed content spoofing possiblity in the default 404 page.

Co-Authored-By: Tim Graham <timograham@gmail.com>
This commit is contained in:
Tom Hacohen 2019-01-04 02:21:55 +00:00 committed by Tim Graham
parent e49ab72637
commit 1ecc0a395b
6 changed files with 56 additions and 11 deletions

View File

@ -1,3 +1,5 @@
from urllib.parse import quote
from django.http import ( from django.http import (
HttpResponseBadRequest, HttpResponseForbidden, HttpResponseNotFound, HttpResponseBadRequest, HttpResponseForbidden, HttpResponseNotFound,
HttpResponseServerError, HttpResponseServerError,
@ -22,7 +24,8 @@ def page_not_found(request, exception, template_name=ERROR_404_TEMPLATE_NAME):
Templates: :template:`404.html` Templates: :template:`404.html`
Context: Context:
request_path 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 exception
The message from the exception which triggered the 404 (if one was The message from the exception which triggered the 404 (if one was
supplied), or the exception class name supplied), or the exception class name
@ -38,7 +41,7 @@ def page_not_found(request, exception, template_name=ERROR_404_TEMPLATE_NAME):
if isinstance(message, str): if isinstance(message, str):
exception_repr = message exception_repr = message
context = { context = {
'request_path': request.path, 'request_path': quote(request.path),
'exception': exception_repr, 'exception': exception_repr,
} }
try: try:
@ -51,7 +54,7 @@ def page_not_found(request, exception, template_name=ERROR_404_TEMPLATE_NAME):
raise raise
template = Engine().from_string( template = Engine().from_string(
'<h1>Not Found</h1>' '<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)) body = template.render(Context(context))
content_type = 'text/html' content_type = 'text/html'
return HttpResponseNotFound(body, content_type=content_type) return HttpResponseNotFound(body, content_type=content_type)

18
docs/releases/1.11.18.txt Normal file
View File

@ -0,0 +1,18 @@
============================
Django 1.11.18 release notes
============================
*January 4, 2019*
Django 1.11.18 fixes a security issue in 1.11.17.
CVE-2019-3498: Content spoofing possibility in the default 404 page
-------------------------------------------------------------------
An attacker could craft a malicious URL that could make spoofed content appear
on the default page generated by the ``django.views.defaults.page_not_found()``
view.
The URL path is no longer displayed in the default 404 template and the
``request_path`` context variable is now quoted to fix the issue for custom
templates that use the path.

View File

@ -2,9 +2,20 @@
Django 2.0.10 release notes Django 2.0.10 release notes
=========================== ===========================
*Release date TBD* *January 4, 2019*
Django 2.0.10 fixes several bugs in 2.0.9. Django 2.0.10 fixes a security issue and several bugs in 2.0.9.
CVE-2019-3498: Content spoofing possibility in the default 404 page
-------------------------------------------------------------------
An attacker could craft a malicious URL that could make spoofed content appear
on the default page generated by the ``django.views.defaults.page_not_found()``
view.
The URL path is no longer displayed in the default 404 template and the
``request_path`` context variable is now quoted to fix the issue for custom
templates that use the path.
Bugfixes Bugfixes
======== ========

View File

@ -2,10 +2,20 @@
Django 2.1.5 release notes Django 2.1.5 release notes
========================== ==========================
*Expected January 1, 2019* *January 4, 2019*
Django 2.1.5 fixes a security issue and several bugs in 2.1.4.
Django 2.1.5 fixes several bugs in 2.1.4. CVE-2019-3498: Content spoofing possibility in the default 404 page
-------------------------------------------------------------------
An attacker could craft a malicious URL that could make spoofed content appear
on the default page generated by the ``django.views.defaults.page_not_found()``
view.
The URL path is no longer displayed in the default 404 template and the
``request_path`` context variable is now quoted to fix the issue for custom
templates that use the path.
Bugfixes Bugfixes
======== ========

View File

@ -61,6 +61,7 @@ versions of the documentation contain the release notes for any later releases.
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1
1.11.18
1.11.17 1.11.17
1.11.16 1.11.16
1.11.15 1.11.15

View File

@ -5,6 +5,7 @@ from django.db import close_old_connections, connection
from django.test import ( from django.test import (
RequestFactory, SimpleTestCase, TransactionTestCase, override_settings, RequestFactory, SimpleTestCase, TransactionTestCase, override_settings,
) )
from django.utils.version import PY37
class HandlerTests(SimpleTestCase): class HandlerTests(SimpleTestCase):
@ -162,16 +163,17 @@ class HandlerRequestTests(SimpleTestCase):
def test_invalid_urls(self): def test_invalid_urls(self):
response = self.client.get('~%A9helloworld') 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/') 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/') 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/') 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): def test_environ_path_info_type(self):
environ = self.request_factory.get('/%E2%A8%87%87%A5%E2%A8%A0').environ environ = self.request_factory.get('/%E2%A8%87%87%A5%E2%A8%A0').environ