Fixed #21098 -- Applied sensitive_post_parameters to MultiValueDict
Thanks simonpercivall for the report and bmispelon for the review.
This commit is contained in:
parent
4f40b97d97
commit
2daada800f
|
@ -11,6 +11,7 @@ from django.http import (HttpResponse, HttpResponseServerError,
|
||||||
HttpResponseNotFound, HttpRequest, build_request_repr)
|
HttpResponseNotFound, HttpRequest, build_request_repr)
|
||||||
from django.template import Template, Context, TemplateDoesNotExist
|
from django.template import Template, Context, TemplateDoesNotExist
|
||||||
from django.template.defaultfilters import force_escape, pprint
|
from django.template.defaultfilters import force_escape, pprint
|
||||||
|
from django.utils.datastructures import MultiValueDict
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.encoding import force_bytes, smart_text
|
from django.utils.encoding import force_bytes, smart_text
|
||||||
from django.utils.module_loading import import_by_path
|
from django.utils.module_loading import import_by_path
|
||||||
|
@ -118,6 +119,20 @@ class SafeExceptionReporterFilter(ExceptionReporterFilter):
|
||||||
"""
|
"""
|
||||||
return settings.DEBUG is False
|
return settings.DEBUG is False
|
||||||
|
|
||||||
|
def get_cleansed_multivaluedict(self, request, multivaluedict):
|
||||||
|
"""
|
||||||
|
Replaces the keys in a MultiValueDict marked as sensitive with stars.
|
||||||
|
This mitigates leaking sensitive POST parameters if something like
|
||||||
|
request.POST['nonexistent_key'] throws an exception (#21098).
|
||||||
|
"""
|
||||||
|
sensitive_post_parameters = getattr(request, 'sensitive_post_parameters', [])
|
||||||
|
if self.is_active(request) and sensitive_post_parameters:
|
||||||
|
multivaluedict = multivaluedict.copy()
|
||||||
|
for param in sensitive_post_parameters:
|
||||||
|
if param in multivaluedict:
|
||||||
|
multivaluedict[param] = CLEANSED_SUBSTITUTE
|
||||||
|
return multivaluedict
|
||||||
|
|
||||||
def get_post_parameters(self, request):
|
def get_post_parameters(self, request):
|
||||||
"""
|
"""
|
||||||
Replaces the values of POST parameters marked as sensitive with
|
Replaces the values of POST parameters marked as sensitive with
|
||||||
|
@ -143,6 +158,15 @@ class SafeExceptionReporterFilter(ExceptionReporterFilter):
|
||||||
else:
|
else:
|
||||||
return request.POST
|
return request.POST
|
||||||
|
|
||||||
|
def cleanse_special_types(self, request, value):
|
||||||
|
if isinstance(value, HttpRequest):
|
||||||
|
# Cleanse the request's POST parameters.
|
||||||
|
value = self.get_request_repr(value)
|
||||||
|
elif isinstance(value, MultiValueDict):
|
||||||
|
# Cleanse MultiValueDicts (request.POST is the one we usually care about)
|
||||||
|
value = self.get_cleansed_multivaluedict(request, value)
|
||||||
|
return value
|
||||||
|
|
||||||
def get_traceback_frame_variables(self, request, tb_frame):
|
def get_traceback_frame_variables(self, request, tb_frame):
|
||||||
"""
|
"""
|
||||||
Replaces the values of variables marked as sensitive with
|
Replaces the values of variables marked as sensitive with
|
||||||
|
@ -173,17 +197,14 @@ class SafeExceptionReporterFilter(ExceptionReporterFilter):
|
||||||
for name, value in tb_frame.f_locals.items():
|
for name, value in tb_frame.f_locals.items():
|
||||||
if name in sensitive_variables:
|
if name in sensitive_variables:
|
||||||
value = CLEANSED_SUBSTITUTE
|
value = CLEANSED_SUBSTITUTE
|
||||||
elif isinstance(value, HttpRequest):
|
else:
|
||||||
# Cleanse the request's POST parameters.
|
value = self.cleanse_special_types(request, value)
|
||||||
value = self.get_request_repr(value)
|
|
||||||
cleansed[name] = value
|
cleansed[name] = value
|
||||||
else:
|
else:
|
||||||
# Potentially cleanse only the request if it's one of the frame variables.
|
# Potentially cleanse the request and any MultiValueDicts if they
|
||||||
|
# are one of the frame variables.
|
||||||
for name, value in tb_frame.f_locals.items():
|
for name, value in tb_frame.f_locals.items():
|
||||||
if isinstance(value, HttpRequest):
|
cleansed[name] = self.cleanse_special_types(request, value)
|
||||||
# Cleanse the request's POST parameters.
|
|
||||||
value = self.get_request_repr(value)
|
|
||||||
cleansed[name] = value
|
|
||||||
|
|
||||||
if (tb_frame.f_code.co_name == 'sensitive_variables_wrapper'
|
if (tb_frame.f_code.co_name == 'sensitive_variables_wrapper'
|
||||||
and 'sensitive_variables_wrapper' in tb_frame.f_locals):
|
and 'sensitive_variables_wrapper' in tb_frame.f_locals):
|
||||||
|
|
|
@ -24,7 +24,8 @@ from django.views.debug import ExceptionReporter
|
||||||
from .. import BrokenException, except_args
|
from .. import BrokenException, except_args
|
||||||
from ..views import (sensitive_view, non_sensitive_view, paranoid_view,
|
from ..views import (sensitive_view, non_sensitive_view, paranoid_view,
|
||||||
custom_exception_reporter_filter_view, sensitive_method_view,
|
custom_exception_reporter_filter_view, sensitive_method_view,
|
||||||
sensitive_args_function_caller, sensitive_kwargs_function_caller)
|
sensitive_args_function_caller, sensitive_kwargs_function_caller,
|
||||||
|
multivalue_dict_key_error)
|
||||||
|
|
||||||
|
|
||||||
@override_settings(DEBUG=True, TEMPLATE_DEBUG=True)
|
@override_settings(DEBUG=True, TEMPLATE_DEBUG=True)
|
||||||
|
@ -511,6 +512,19 @@ class ExceptionReporterFilterTests(TestCase, ExceptionReportTestMixin):
|
||||||
self.verify_paranoid_response(paranoid_view)
|
self.verify_paranoid_response(paranoid_view)
|
||||||
self.verify_paranoid_email(paranoid_view)
|
self.verify_paranoid_email(paranoid_view)
|
||||||
|
|
||||||
|
def test_multivalue_dict_key_error(self):
|
||||||
|
"""
|
||||||
|
#21098 -- Ensure that sensitive POST parameters cannot be seen in the
|
||||||
|
error reports for if request.POST['nonexistent_key'] throws an error.
|
||||||
|
"""
|
||||||
|
with self.settings(DEBUG=True):
|
||||||
|
self.verify_unsafe_response(multivalue_dict_key_error)
|
||||||
|
self.verify_unsafe_email(multivalue_dict_key_error)
|
||||||
|
|
||||||
|
with self.settings(DEBUG=False):
|
||||||
|
self.verify_safe_response(multivalue_dict_key_error)
|
||||||
|
self.verify_safe_email(multivalue_dict_key_error)
|
||||||
|
|
||||||
def test_custom_exception_reporter_filter(self):
|
def test_custom_exception_reporter_filter(self):
|
||||||
"""
|
"""
|
||||||
Ensure that it's possible to assign an exception reporter filter to
|
Ensure that it's possible to assign an exception reporter filter to
|
||||||
|
|
|
@ -289,3 +289,16 @@ class Klass(object):
|
||||||
|
|
||||||
def sensitive_method_view(request):
|
def sensitive_method_view(request):
|
||||||
return Klass().method(request)
|
return Klass().method(request)
|
||||||
|
|
||||||
|
|
||||||
|
@sensitive_variables('sauce')
|
||||||
|
@sensitive_post_parameters('bacon-key', 'sausage-key')
|
||||||
|
def multivalue_dict_key_error(request):
|
||||||
|
cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd'])
|
||||||
|
sauce = ''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e'])
|
||||||
|
try:
|
||||||
|
request.POST['bar']
|
||||||
|
except Exception:
|
||||||
|
exc_info = sys.exc_info()
|
||||||
|
send_log(request, exc_info)
|
||||||
|
return technical_500_response(request, *exc_info)
|
||||||
|
|
Loading…
Reference in New Issue