Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #21098 -- Applied sensitive_post_parameters to MultiValueDict

Thanks simonpercivall for the report and bmispelon for the review.
  • Loading branch information...
commit 2daada800f8e28cc1ba664b3008efaefab8fb570 1 parent 4f40b97
Tim Graham authored September 17, 2013
37  django/views/debug.py
@@ -11,6 +11,7 @@
11 11
     HttpResponseNotFound, HttpRequest, build_request_repr)
12 12
 from django.template import Template, Context, TemplateDoesNotExist
13 13
 from django.template.defaultfilters import force_escape, pprint
  14
+from django.utils.datastructures import MultiValueDict
14 15
 from django.utils.html import escape
15 16
 from django.utils.encoding import force_bytes, smart_text
16 17
 from django.utils.module_loading import import_by_path
@@ -118,6 +119,20 @@ def is_active(self, request):
118 119
         """
119 120
         return settings.DEBUG is False
120 121
 
  122
+    def get_cleansed_multivaluedict(self, request, multivaluedict):
  123
+        """
  124
+        Replaces the keys in a MultiValueDict marked as sensitive with stars.
  125
+        This mitigates leaking sensitive POST parameters if something like
  126
+        request.POST['nonexistent_key'] throws an exception (#21098).
  127
+        """
  128
+        sensitive_post_parameters = getattr(request, 'sensitive_post_parameters', [])
  129
+        if self.is_active(request) and sensitive_post_parameters:
  130
+            multivaluedict = multivaluedict.copy()
  131
+            for param in sensitive_post_parameters:
  132
+                if param in multivaluedict:
  133
+                    multivaluedict[param] = CLEANSED_SUBSTITUTE
  134
+        return multivaluedict
  135
+
121 136
     def get_post_parameters(self, request):
122 137
         """
123 138
         Replaces the values of POST parameters marked as sensitive with
@@ -143,6 +158,15 @@ def get_post_parameters(self, request):
143 158
             else:
144 159
                 return request.POST
145 160
 
  161
+    def cleanse_special_types(self, request, value):
  162
+        if isinstance(value, HttpRequest):
  163
+            # Cleanse the request's POST parameters.
  164
+            value = self.get_request_repr(value)
  165
+        elif isinstance(value, MultiValueDict):
  166
+            # Cleanse MultiValueDicts (request.POST is the one we usually care about)
  167
+            value = self.get_cleansed_multivaluedict(request, value)
  168
+        return value
  169
+
146 170
     def get_traceback_frame_variables(self, request, tb_frame):
147 171
         """
148 172
         Replaces the values of variables marked as sensitive with
@@ -173,17 +197,14 @@ def get_traceback_frame_variables(self, request, tb_frame):
173 197
                 for name, value in tb_frame.f_locals.items():
174 198
                     if name in sensitive_variables:
175 199
                         value = CLEANSED_SUBSTITUTE
176  
-                    elif isinstance(value, HttpRequest):
177  
-                        # Cleanse the request's POST parameters.
178  
-                        value = self.get_request_repr(value)
  200
+                    else:
  201
+                        value = self.cleanse_special_types(request, value)
179 202
                     cleansed[name] = value
180 203
         else:
181  
-            # Potentially cleanse only the request if it's one of the frame variables.
  204
+            # Potentially cleanse the request and any MultiValueDicts if they
  205
+            # are one of the frame variables.
182 206
             for name, value in tb_frame.f_locals.items():
183  
-                if isinstance(value, HttpRequest):
184  
-                    # Cleanse the request's POST parameters.
185  
-                    value = self.get_request_repr(value)
186  
-                cleansed[name] = value
  207
+                cleansed[name] = self.cleanse_special_types(request, value)
187 208
 
188 209
         if (tb_frame.f_code.co_name == 'sensitive_variables_wrapper'
189 210
             and 'sensitive_variables_wrapper' in tb_frame.f_locals):
16  tests/view_tests/tests/test_debug.py
@@ -24,7 +24,8 @@
24 24
 from .. import BrokenException, except_args
25 25
 from ..views import (sensitive_view, non_sensitive_view, paranoid_view,
26 26
     custom_exception_reporter_filter_view, sensitive_method_view,
27  
-    sensitive_args_function_caller, sensitive_kwargs_function_caller)
  27
+    sensitive_args_function_caller, sensitive_kwargs_function_caller,
  28
+    multivalue_dict_key_error)
28 29
 
29 30
 
30 31
 @override_settings(DEBUG=True, TEMPLATE_DEBUG=True)
@@ -511,6 +512,19 @@ def test_paranoid_request(self):
511 512
             self.verify_paranoid_response(paranoid_view)
512 513
             self.verify_paranoid_email(paranoid_view)
513 514
 
  515
+    def test_multivalue_dict_key_error(self):
  516
+        """
  517
+        #21098 -- Ensure that sensitive POST parameters cannot be seen in the
  518
+        error reports for if request.POST['nonexistent_key'] throws an error.
  519
+        """
  520
+        with self.settings(DEBUG=True):
  521
+            self.verify_unsafe_response(multivalue_dict_key_error)
  522
+            self.verify_unsafe_email(multivalue_dict_key_error)
  523
+
  524
+        with self.settings(DEBUG=False):
  525
+            self.verify_safe_response(multivalue_dict_key_error)
  526
+            self.verify_safe_email(multivalue_dict_key_error)
  527
+
514 528
     def test_custom_exception_reporter_filter(self):
515 529
         """
516 530
         Ensure that it's possible to assign an exception reporter filter to
13  tests/view_tests/views.py
@@ -289,3 +289,16 @@ def method(self, request):
289 289
 
290 290
 def sensitive_method_view(request):
291 291
     return Klass().method(request)
  292
+
  293
+
  294
+@sensitive_variables('sauce')
  295
+@sensitive_post_parameters('bacon-key', 'sausage-key')
  296
+def multivalue_dict_key_error(request):
  297
+    cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd'])
  298
+    sauce = ''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e'])
  299
+    try:
  300
+        request.POST['bar']
  301
+    except Exception:
  302
+        exc_info = sys.exc_info()
  303
+        send_log(request, exc_info)
  304
+        return technical_500_response(request, *exc_info)

0 notes on commit 2daada8

Please sign in to comment.
Something went wrong with that request. Please try again.