﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
28215	sensitive_post_parameters/sensitive_variables leaking sensitive values into the http 500 exception email	Peter Zsoldos	Ahmed Nassar	"== tl;dr

despite using sensitive_xxx decorator, sensitive data can end up in the 500 error emails Django sends, as these decorators only protect the data inside the very function they are decorated 

== repro

{{{#!python
class ReproTestCase(TransactionTestCase):

    def test_when_login_view_raises_an_exception_password_is_not_in_the_500_email(self):  # noqa: E501
        password = '$0m3 P4$$w0rd'
        exception_email_html_body = self.get_500_email_html_for_login_error(
            username='some_user', password=password
        )
        self.assertNotIn(
                member=password, container=exception_email_html_body)

    def get_500_email_html_for_login_error(self, username, password):
        # patch this methodd so AuthenticationForm.clean is
        # called which has local password variable
        login_view_raising_value_error = patch(
            'django.contrib.auth.forms.authenticate',
            side_effect=ValueError('some error')
        )

        self.goto_login_page()

        with TestClientNotRaisingExceptionButCapturing(self.client) as capture:  # see implementation details in attachment
            with login_view_raising_value_error:
                self.submit_login(username=username, password=password)

        request = capture.get_captured_request()
        exc_type, exc_value, tb = capture.stored_exc_info
        # based on django.utils.log.AdminEmailHandler.emit
        reporter = ExceptionReporter(
            request=request, is_email=True,
            exc_type=exc_type, exc_value=exc_value, tb=tb)
        self.assertTrue(reporter.filter.is_active(request))
        return reporter.get_traceback_html()
}}}

Is attached for all current supported Django versions (1.8, 1.10, 1.11), simply unpack and run `tox`

The test can seem complicated due to the limitations of the test client in testing 500 responses - see #18707

== why I think it is an issue

While I'm aware of the [disclaimers in the documentation about filtering sensitive data (https://docs.djangoproject.com/en/1.8/howto/error-reporting/#custom-error-reports), because of the impact of it - even on users who don't explicitly use any of the `sensitive_x` decorators themselves, I think it is a leak that should be stopped.

* typical sensitive data is passwords. We have discovered this issue due to a bug in our custom authentication backend. These passwords could also be used beyond just the single Django system - whether because of single sign on solutions like LDAP/active-directory, or simply because users might reuse their passwords across sites
* exception emails might be sent through third party providers, which may keep track of the sent message body. Internal IT departments might also be considered such 3rd parties too.
* support people (admins receiving 500 emails) see supposedly private data

== potential solution ideas (which might be wrong of course :))

=== writing a custom exception filter


   * simply don't report any variables once encountered - https://gist.github.com/zsoldosp/5710abaa9dedc03417d60bcc714c95d4
   * keep track of protected variable names and replace those in frames further down the stack (i.e.: if parameter 'password', is sensitive, cleanse variables names 'password' too in all methods)

=== wrapping sensitive variables into a special object

Instead of just using the sensitive data in reporting, wrap these variables in an object that has 'contains_sensitive_data' attribute, i.e.: if it is stored into another variable, as it is a 'pointer' to the original, it will have that attribute, and thus can be filtered out in the exception report.

This isn't perfect either, e.g.: `password = password.strip()`, though by overriding a lot of methods or using `__getattr__` magic, it could work. Might only be 'reasonable' to do so for request parameters, as there at least we know the limited set of variable types we receive

{{{#!python
  @sensitive_request_params
  def view(request):
      ....

  # inside sensitive_request_params
  for sensitive_variable_name in sensitive_variable_names:
      if sensitive_variable_name in request.POST:
         request.POST[sensitive_variable_name] = SensitiveVariable(request.POST[sensitive_variable_name])
      ....
  }}}"	Bug	assigned	Error reporting	dev	Normal			Calvin Vu Ülgen Sarıkavak	Accepted	0	0	0	0	1	0
