mirror of
https://github.com/django/django.git
synced 2025-06-12 15:09:12 +00:00
[4.2.x] Refs CVE-2025-48432 -- Prevented log injection in remaining response logging.
Migrated remaining response-related logging to use the `log_response()` helper to avoid potential log injection, to ensure untrusted values like request paths are safely escaped. Co-authored-by: Natalia <124304+nessita@users.noreply.github.com> Backport of 957951755259b412d5113333b32bf85871d29814 from main.
This commit is contained in:
parent
10ba3f78da
commit
b597d46bb1
@ -14,6 +14,7 @@ from django.template.response import TemplateResponse
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.decorators import classonlymethod
|
from django.utils.decorators import classonlymethod
|
||||||
from django.utils.functional import classproperty
|
from django.utils.functional import classproperty
|
||||||
|
from django.utils.log import log_response
|
||||||
|
|
||||||
logger = logging.getLogger("django.request")
|
logger = logging.getLogger("django.request")
|
||||||
|
|
||||||
@ -143,13 +144,14 @@ class View:
|
|||||||
return handler(request, *args, **kwargs)
|
return handler(request, *args, **kwargs)
|
||||||
|
|
||||||
def http_method_not_allowed(self, request, *args, **kwargs):
|
def http_method_not_allowed(self, request, *args, **kwargs):
|
||||||
logger.warning(
|
response = HttpResponseNotAllowed(self._allowed_methods())
|
||||||
|
log_response(
|
||||||
"Method Not Allowed (%s): %s",
|
"Method Not Allowed (%s): %s",
|
||||||
request.method,
|
request.method,
|
||||||
request.path,
|
request.path,
|
||||||
extra={"status_code": 405, "request": request},
|
response=response,
|
||||||
|
request=request,
|
||||||
)
|
)
|
||||||
response = HttpResponseNotAllowed(self._allowed_methods())
|
|
||||||
|
|
||||||
if self.view_is_async:
|
if self.view_is_async:
|
||||||
|
|
||||||
@ -261,10 +263,9 @@ class RedirectView(View):
|
|||||||
else:
|
else:
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
response = HttpResponseGone()
|
||||||
"Gone: %s", request.path, extra={"status_code": 410, "request": request}
|
log_response("Gone: %s", request.path, response=response, request=request)
|
||||||
)
|
return response
|
||||||
return HttpResponseGone()
|
|
||||||
|
|
||||||
def head(self, request, *args, **kwargs):
|
def head(self, request, *args, **kwargs):
|
||||||
return self.get(request, *args, **kwargs)
|
return self.get(request, *args, **kwargs)
|
||||||
|
14
docs/releases/4.2.23.txt
Normal file
14
docs/releases/4.2.23.txt
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
===========================
|
||||||
|
Django 4.2.23 release notes
|
||||||
|
===========================
|
||||||
|
|
||||||
|
*June 10, 2025*
|
||||||
|
|
||||||
|
Django 4.2.23 fixes a potential log injection issue in 4.2.22.
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
========
|
||||||
|
|
||||||
|
* Fixed a log injection possibility by migrating remaining response logging
|
||||||
|
to ``django.utils.log.log_response()``, which safely escapes arguments such
|
||||||
|
as the request path to prevent unsafe log output (:cve:`2025-48432`).
|
@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases.
|
|||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
|
4.2.23
|
||||||
4.2.22
|
4.2.22
|
||||||
4.2.21
|
4.2.21
|
||||||
4.2.20
|
4.2.20
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
import logging
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from logging_tests.tests import LoggingAssertionMixin
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.test import RequestFactory, SimpleTestCase, override_settings
|
from django.test import RequestFactory, SimpleTestCase, override_settings
|
||||||
@ -63,7 +66,7 @@ class InstanceView(View):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
class ViewTest(SimpleTestCase):
|
class ViewTest(LoggingAssertionMixin, SimpleTestCase):
|
||||||
rf = RequestFactory()
|
rf = RequestFactory()
|
||||||
|
|
||||||
def _assert_simple(self, response):
|
def _assert_simple(self, response):
|
||||||
@ -297,6 +300,25 @@ class ViewTest(SimpleTestCase):
|
|||||||
response = view.dispatch(self.rf.head("/"))
|
response = view.dispatch(self.rf.head("/"))
|
||||||
self.assertEqual(response.status_code, 405)
|
self.assertEqual(response.status_code, 405)
|
||||||
|
|
||||||
|
def test_method_not_allowed_response_logged(self):
|
||||||
|
for path, escaped in [
|
||||||
|
("/foo/", "/foo/"),
|
||||||
|
(r"/%1B[1;31mNOW IN RED!!!1B[0m/", r"/\x1b[1;31mNOW IN RED!!!1B[0m/"),
|
||||||
|
]:
|
||||||
|
with self.subTest(path=path):
|
||||||
|
request = self.rf.get(path, REQUEST_METHOD="BOGUS")
|
||||||
|
with self.assertLogs("django.request", "WARNING") as handler:
|
||||||
|
response = SimpleView.as_view()(request)
|
||||||
|
|
||||||
|
self.assertLogRecord(
|
||||||
|
handler,
|
||||||
|
f"Method Not Allowed (BOGUS): {escaped}",
|
||||||
|
logging.WARNING,
|
||||||
|
405,
|
||||||
|
request,
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 405)
|
||||||
|
|
||||||
|
|
||||||
@override_settings(ROOT_URLCONF="generic_views.urls")
|
@override_settings(ROOT_URLCONF="generic_views.urls")
|
||||||
class TemplateViewTest(SimpleTestCase):
|
class TemplateViewTest(SimpleTestCase):
|
||||||
@ -425,7 +447,7 @@ class TemplateViewTest(SimpleTestCase):
|
|||||||
|
|
||||||
|
|
||||||
@override_settings(ROOT_URLCONF="generic_views.urls")
|
@override_settings(ROOT_URLCONF="generic_views.urls")
|
||||||
class RedirectViewTest(SimpleTestCase):
|
class RedirectViewTest(LoggingAssertionMixin, SimpleTestCase):
|
||||||
rf = RequestFactory()
|
rf = RequestFactory()
|
||||||
|
|
||||||
def test_no_url(self):
|
def test_no_url(self):
|
||||||
@ -549,6 +571,20 @@ class RedirectViewTest(SimpleTestCase):
|
|||||||
response = view.dispatch(self.rf.head("/foo/"))
|
response = view.dispatch(self.rf.head("/foo/"))
|
||||||
self.assertEqual(response.status_code, 410)
|
self.assertEqual(response.status_code, 410)
|
||||||
|
|
||||||
|
def test_gone_response_logged(self):
|
||||||
|
for path, escaped in [
|
||||||
|
("/foo/", "/foo/"),
|
||||||
|
(r"/%1B[1;31mNOW IN RED!!!1B[0m/", r"/\x1b[1;31mNOW IN RED!!!1B[0m/"),
|
||||||
|
]:
|
||||||
|
with self.subTest(path=path):
|
||||||
|
request = self.rf.get(path)
|
||||||
|
with self.assertLogs("django.request", "WARNING") as handler:
|
||||||
|
RedirectView().dispatch(request)
|
||||||
|
|
||||||
|
self.assertLogRecord(
|
||||||
|
handler, f"Gone: {escaped}", logging.WARNING, 410, request
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class GetContextDataTest(SimpleTestCase):
|
class GetContextDataTest(SimpleTestCase):
|
||||||
def test_get_context_data_super(self):
|
def test_get_context_data_super(self):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user