Opened 8 years ago

Last modified 9 days ago

#28215 assigned Bug

sensitive_post_parameters/sensitive_variables leaking sensitive values into the http 500 exception email — at Version 4

Reported by: Peter Zsoldos Owned by:
Component: Error reporting Version: dev
Severity: Normal Keywords:
Cc: Calvin Vu, Ülgen Sarıkavak Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: yes
Easy pickings: yes UI/UX: no

Description (last modified by Peter Zsoldos)

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

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

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

  @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])
      ....

Change History (5)

by Peter Zsoldos, 8 years ago

a minimal tox.ini/django project to repro the case

comment:1 by Peter Zsoldos, 8 years ago

Description: modified (diff)

comment:2 by Peter Zsoldos, 8 years ago

Version: 1.111.8

comment:3 by Tim Graham, 8 years ago

It would be helpful to give a high level overview of the issue in the ticket's summary and description. The current summary is rather general and the description doesn't detail the cause in much detail. Currently, I have to open the sample project, unzip it, and read the code to try to understand the issue.

If I'm reading it correctly, it looks like this particular case could be fixed by marking credentials as a sensitive variable in contrib.auth.authenticate() -- is that correct?

comment:4 by Peter Zsoldos, 8 years ago

Description: modified (diff)
Note: See TracTickets for help on using tickets.
Back to Top