Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Fixed #19453 -- Obfuscated args in sensitive_variables's frame. #613

Closed
wants to merge 1 commit into from

2 participants

@aaugustin
Owner

Patch by Julien Phalip.

@jphalip
Collaborator

This got pushed in 9180146

@jphalip jphalip closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Dec 30, 2012
  1. @aaugustin

    Fixed #19453 -- Obfuscated args in sensitive_variables's frame.

    aaugustin authored
    Patch by Julien Phalip.
This page is out of date. Refresh to see the latest.
View
22 django/views/debug.py
@@ -172,13 +172,12 @@ def get_traceback_frame_variables(self, request, tb_frame):
break
current_frame = current_frame.f_back
- cleansed = []
+ cleansed = {}
if self.is_active(request) and sensitive_variables:
if sensitive_variables == '__ALL__':
# Cleanse all variables
for name, value in tb_frame.f_locals.items():
- cleansed.append((name, CLEANSED_SUBSTITUTE))
- return cleansed
+ cleansed[name] = CLEANSED_SUBSTITUTE
else:
# Cleanse specified variables
for name, value in tb_frame.f_locals.items():
@@ -187,16 +186,25 @@ def get_traceback_frame_variables(self, request, tb_frame):
elif isinstance(value, HttpRequest):
# Cleanse the request's POST parameters.
value = self.get_request_repr(value)
- cleansed.append((name, value))
- return cleansed
+ cleansed[name] = value
else:
# Potentially cleanse only the request if it's one of the frame variables.
for name, value in tb_frame.f_locals.items():
if isinstance(value, HttpRequest):
# Cleanse the request's POST parameters.
value = self.get_request_repr(value)
- cleansed.append((name, value))
- return cleansed
+ cleansed[name] = value
+
+ if (tb_frame.f_code.co_name == 'sensitive_variables_wrapper'
+ and 'sensitive_variables_wrapper' in tb_frame.f_locals):
+ # For good measure, obfuscate the decorated function's arguments in
+ # the sensitive_variables decorator's frame, in case the variables
+ # associated with those arguments were meant to be obfuscated from
+ # the decorated function's frame.
+ cleansed['func_args'] = CLEANSED_SUBSTITUTE
+ cleansed['func_kwargs'] = CLEANSED_SUBSTITUTE
+
+ return cleansed.items()
class ExceptionReporter(object):
"""
View
4 django/views/decorators/debug.py
@@ -26,12 +26,12 @@ def my_function()
"""
def decorator(func):
@functools.wraps(func)
- def sensitive_variables_wrapper(*args, **kwargs):
+ def sensitive_variables_wrapper(*func_args, **func_kwargs):
if variables:
sensitive_variables_wrapper.sensitive_variables = variables
else:
sensitive_variables_wrapper.sensitive_variables = '__ALL__'
- return func(*args, **kwargs)
+ return func(*func_args, **func_kwargs)
return sensitive_variables_wrapper
return decorator
View
92 tests/regressiontests/views/tests/debug.py
@@ -7,7 +7,6 @@
import os
import sys
-from django.conf import settings
from django.core import mail
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.urlresolvers import reverse
@@ -19,7 +18,8 @@
from .. import BrokenException, except_args
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)
@override_settings(DEBUG=True, TEMPLATE_DEBUG=True)
@@ -306,17 +306,28 @@ def verify_unsafe_email(self, view, check_for_POST_params=True):
response = view(request)
self.assertEqual(len(mail.outbox), 1)
email = mail.outbox[0]
+
# Frames vars are never shown in plain text email reports.
- body = force_text(email.body)
- self.assertNotIn('cooked_eggs', body)
- self.assertNotIn('scrambled', body)
- self.assertNotIn('sauce', body)
- self.assertNotIn('worcestershire', body)
+ body_plain = force_text(email.body)
+ self.assertNotIn('cooked_eggs', body_plain)
+ self.assertNotIn('scrambled', body_plain)
+ self.assertNotIn('sauce', body_plain)
+ self.assertNotIn('worcestershire', body_plain)
+
+ # Frames vars are shown in html email reports.
+ body_html = force_text(email.alternatives[0][0])
+ self.assertIn('cooked_eggs', body_html)
+ self.assertIn('scrambled', body_html)
+ self.assertIn('sauce', body_html)
+ self.assertIn('worcestershire', body_html)
+
if check_for_POST_params:
for k, v in self.breakfast_data.items():
# All POST parameters are shown.
- self.assertIn(k, body)
- self.assertIn(v, body)
+ self.assertIn(k, body_plain)
+ self.assertIn(v, body_plain)
+ self.assertIn(k, body_html)
+ self.assertIn(v, body_html)
def verify_safe_email(self, view, check_for_POST_params=True):
"""
@@ -328,22 +339,35 @@ def verify_safe_email(self, view, check_for_POST_params=True):
response = view(request)
self.assertEqual(len(mail.outbox), 1)
email = mail.outbox[0]
+
# Frames vars are never shown in plain text email reports.
- body = force_text(email.body)
- self.assertNotIn('cooked_eggs', body)
- self.assertNotIn('scrambled', body)
- self.assertNotIn('sauce', body)
- self.assertNotIn('worcestershire', body)
+ body_plain = force_text(email.body)
+ self.assertNotIn('cooked_eggs', body_plain)
+ self.assertNotIn('scrambled', body_plain)
+ self.assertNotIn('sauce', body_plain)
+ self.assertNotIn('worcestershire', body_plain)
+
+ # Frames vars are shown in html email reports.
+ body_html = force_text(email.alternatives[0][0])
+ self.assertIn('cooked_eggs', body_html)
+ self.assertIn('scrambled', body_html)
+ self.assertIn('sauce', body_html)
+ self.assertNotIn('worcestershire', body_html)
+
if check_for_POST_params:
for k, v in self.breakfast_data.items():
# All POST parameters' names are shown.
- self.assertIn(k, body)
+ self.assertIn(k, body_plain)
# Non-sensitive POST parameters' values are shown.
- self.assertIn('baked-beans-value', body)
- self.assertIn('hash-brown-value', body)
+ self.assertIn('baked-beans-value', body_plain)
+ self.assertIn('hash-brown-value', body_plain)
+ self.assertIn('baked-beans-value', body_html)
+ self.assertIn('hash-brown-value', body_html)
# Sensitive POST parameters' values are not shown.
- self.assertNotIn('sausage-value', body)
- self.assertNotIn('bacon-value', body)
+ self.assertNotIn('sausage-value', body_plain)
+ self.assertNotIn('bacon-value', body_plain)
+ self.assertNotIn('sausage-value', body_html)
+ self.assertNotIn('bacon-value', body_html)
def verify_paranoid_email(self, view):
"""
@@ -445,6 +469,36 @@ def test_sensitive_method(self):
self.verify_safe_email(sensitive_method_view,
check_for_POST_params=False)
+ def test_sensitive_function_arguments(self):
+ """
+ Ensure that sensitive variables don't leak in the sensitive_variables
+ decorator's frame, when those variables are passed as arguments to the
+ decorated function.
+ Refs #19453.
+ """
+ with self.settings(DEBUG=True):
+ self.verify_unsafe_response(sensitive_args_function_caller)
+ self.verify_unsafe_email(sensitive_args_function_caller)
+
+ with self.settings(DEBUG=False):
+ self.verify_safe_response(sensitive_args_function_caller, check_for_POST_params=False)
+ self.verify_safe_email(sensitive_args_function_caller, check_for_POST_params=False)
+
+ def test_sensitive_function_keyword_arguments(self):
+ """
+ Ensure that sensitive variables don't leak in the sensitive_variables
+ decorator's frame, when those variables are passed as keyword arguments
+ to the decorated function.
+ Refs #19453.
+ """
+ with self.settings(DEBUG=True):
+ self.verify_unsafe_response(sensitive_kwargs_function_caller)
+ self.verify_unsafe_email(sensitive_kwargs_function_caller)
+
+ with self.settings(DEBUG=False):
+ self.verify_safe_response(sensitive_kwargs_function_caller, check_for_POST_params=False)
+ self.verify_safe_email(sensitive_kwargs_function_caller, check_for_POST_params=False)
+
class AjaxResponseExceptionReporterFilter(TestCase, ExceptionReportTestMixin):
"""
View
33 tests/regressiontests/views/views.py
@@ -132,6 +132,7 @@ def send_log(request, exc_info):
][0]
orig_filters = admin_email_handler.filters
admin_email_handler.filters = []
+ admin_email_handler.include_html = True
logger.error('Internal Server Error: %s', request.path,
exc_info=exc_info,
extra={
@@ -184,6 +185,38 @@ def paranoid_view(request):
send_log(request, exc_info)
return technical_500_response(request, *exc_info)
+def sensitive_args_function_caller(request):
+ try:
+ sensitive_args_function(''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e']))
+ except Exception:
+ exc_info = sys.exc_info()
+ send_log(request, exc_info)
+ return technical_500_response(request, *exc_info)
+
+@sensitive_variables('sauce')
+def sensitive_args_function(sauce):
+ # Do not just use plain strings for the variables' values in the code
+ # so that the tests don't return false positives when the function's source
+ # is displayed in the exception report.
+ cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd'])
+ raise Exception
+
+def sensitive_kwargs_function_caller(request):
+ try:
+ sensitive_kwargs_function(''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e']))
+ except Exception:
+ exc_info = sys.exc_info()
+ send_log(request, exc_info)
+ return technical_500_response(request, *exc_info)
+
+@sensitive_variables('sauce')
+def sensitive_kwargs_function(sauce=None):
+ # Do not just use plain strings for the variables' values in the code
+ # so that the tests don't return false positives when the function's source
+ # is displayed in the exception report.
+ cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd'])
+ raise Exception
+
class UnsafeExceptionReporterFilter(SafeExceptionReporterFilter):
"""
Ignores all the filtering done by its parent class.
Something went wrong with that request. Please try again.