[2.0.x] 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.
This commit is contained in:
Tom Hacohen 2019-01-04 02:21:55 +00:00 committed by Tim Graham
parent f167f308ff
commit 9f4ed7c94c
5 changed files with 46 additions and 9 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

@ -42,6 +42,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

@ -1,3 +1,4 @@
import sys
import unittest import unittest
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
@ -13,6 +14,8 @@ try:
except ImportError: # Python < 3.5 except ImportError: # Python < 3.5
HTTPStatus = None HTTPStatus = None
PY37 = sys.version_info >= (3, 7, 0)
class HandlerTests(SimpleTestCase): class HandlerTests(SimpleTestCase):
@ -167,16 +170,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 = RequestFactory().get('/%E2%A8%87%87%A5%E2%A8%A0').environ environ = RequestFactory().get('/%E2%A8%87%87%A5%E2%A8%A0').environ