📜 ⬆️ ⬇️

Django: Generating secure error reports on the site

As you know, Django provides a very easy and simple mechanism for notifying developers of any problems. When the project is deployed on the local computer and in the settings DEBUG is set to True, then error reports are simply output as an HTTP response, as a convenient page with the ability to copy traceback.

If this is a production server, and DEBUG is False, then the default reports are sent by e-mail to everyone specified in the ADMINS setting (by the way, if you use an SMTP server, then the letters may not come, because the SMTP server does not accepts root @ localhost — in this case, simply specify any other address that your SMTP server will accept using the SERVER_EMAIL setting).

Of course, nothing prevents you from also writing your own logging handler ( logging handler ) and saving error reports in any way you want — creating a task in a bug tracker, for example.
')
Nevertheless, if the safety of your users is important to you, then a completely natural question arises - how to make the error reports safe for them? That is, how to make sure that no personal information is stored in them, and not sent to anyone by mail (after all, it’s not even the fact that some of the developers may behave in bad faith, but rather that such information in general it is better not to save anywhere outside the server - because the mailbox can be hacked, and the server is usually better protected).

In fact, this problem is very easily solved in Django, and the solution is described almost entirely in the How-to section of the official documentation.

For example, take a simple view for authorization:

from django.http import HttpResponse, HttpResponseRedirect from django.core.urlresolvers import reverse from django.contrib.auth import authenticate, login def login_view(request): if request.method != "POST": return HttpResponse("Please use POST.") user = authenticate( email=request.POST.get("email"), password=request.POST.get("password") ) if user is not None: if user.is_active: login(request, user) status = "ok" else: status = "account_disabled" else: status = "invalid_credentials" if status != "ok": return HttpResponse(status) return HttpResponseRedirect(reverse('app.views.index')) 

If you test yourself, do not forget to either change the email to the username, or add an authorization backend to log in using the email address:

 from django.contrib.auth.backends import ModelBackend from django.contrib.admin.models import User class EmailAuthBackend(ModelBackend): def authenticate(self, email=None, password=None, **kwargs): try: user = User.objects.get(email=email) except User.DoesNotExist: return None except User.MultipleObjectsReturned: user = User.objects.filter(email=email)[0] if user.check_password(password): return user return None 

And now let's see what happens if any error occurs. In practice, the error here is likely to be due to the inaccessibility of the database, but for the test you can simply add an exception call at the beginning of the function:

 raise Exception 

Now, if someone tries to log in, then a report arrives by mail, containing, in particular, information on all POST parameters of the request:

 POST: <QueryDict: {u'csrfmiddlewaretoken ': [u'F3d71EHWECfavaeK4H7nUTzLwgY07AHT'],
                   u'password ': [u'123'],
                   u'email ': [u'aruseni.magiku@gmail.com']}>

Well, well, here and email, and the password. And now let's try to wrap the function in the sensitive_post_parameters decorator:

 from django.views.decorators.debug import sensitive_post_parameters @sensitive_post_parameters("password") def login_view(request): 

Not bad, now instead of a password, 20 stars are included in the report (********************):

 POST: <QueryDict: {u'csrfmiddlewaretoken ': [u'F3d71EHWECfavaeK4H7nUTzLwgY07AHT'],
                   u'password ': [u' ********************* '],
                   u'email ': [u'aruseni.magiku@gmail.com']}>

By the way, the decorator sensitive_post_parameters can take several arguments at once (depending on how many POST parameters you want to hide in the report). And it is possible not to specify arguments at all:

 from django.views.decorators.debug import sensitive_post_parameters @sensitive_post_parameters() def login_view(request): 

In this case, in the error report, the values ​​of all the POST parameters are hidden in general:

 POST: <QueryDict: {u'csrfmiddlewaretoken ': [u' ********************],
                   u'password ': [u' ********************* '],
                   u'email ': [u' ********************* ']}>

But personal information, the possibility of disclosure of which must be prevented, may be contained not only in the POST parameters passed in the request, but also, for example, in local variables that the function defines (and whose values ​​are included in error reports). Imagine, for example, that you have a function process_payment, which, in particular, receives the user's bank card number from the database and writes it to the local variable payment_card_id. Obviously, the value of this variable must be hidden in the error report.

This can be done using the decorator sensitive_variables:

 from django.views.decorators.debug import sensitive_variables @sensitive_variables("payment_card_id") def process_payment(request): 

Like sensitive_post_parameters, sensitive_variables supports the use of multiple arguments (to hide more variables), as well as use without any arguments (to hide all local variables of a function).

However, there still remains some sensitive information - cookies. Which, in particular, contain the session identifier (and the possible interception of the session identifier is very bad).

 COOKIES: {'csrftoken': 'F3d71EHWECfavaeK4H7nUTzLwgY07AHT',
          'sessionid': '262661787a7f42e787ad18ee853ef8d6'}

Well, this is a little more difficult, but not significant.

Add the debug.py file to the application (here it is called “app”, you may have something else) and add your own error report filtering class there (it will inherit from the SafeExceptionReporterFilter class, which is used for filtering when The decorators (sensitive_post_parameters and sensitive_variables) were used:

 from django.views.debug import SafeExceptionReporterFilter from django.http import build_request_repr class CustomExceptionReporterFilter(SafeExceptionReporterFilter): def get_cookies(self, request): if request is None: return {} else: cleansed = request.COOKIES.copy() for key, value in cleansed.iteritems(): cleansed[key] = "secret" return cleansed def get_request_repr(self, request): if request is None: return repr(None) else: return build_request_repr(request, POST_override=self.get_post_parameters(request), COOKIES_override=self.get_cookies(request)) 

And specify in the settings that this particular class should be used for filtering:

 DEFAULT_EXCEPTION_REPORTER_FILTER = 'app.debug.CustomExceptionReporterFilter' 

Well, now it's much better:

 COOKIES: {'csrftoken': 'secret',
          'sessionid': 'secret',
          'timezone': 'secret'}

However, cookie values ​​are still present in the META-META ["CSRF_COOKIE"] dictionary and META ["HTTP_COOKIE"]. Well, let's get them out of there. :)

 from django.views.debug import SafeExceptionReporterFilter from django.http import build_request_repr class CustomExceptionReporterFilter(SafeExceptionReporterFilter): def get_cookies(self, request): if request is None: return {} else: cleansed = request.COOKIES.copy() for key, value in cleansed.iteritems(): cleansed[key] = "secret" return cleansed def get_meta(self, request): if request is None: return {} else: cleansed = request.META.copy() for key in ("HTTP_COOKIE", "CSRF_COOKIE"): cleansed[key] = "secret" return cleansed def get_request_repr(self, request): if request is None: return repr(None) else: return build_request_repr(request, POST_override=self.get_post_parameters(request), COOKIES_override=self.get_cookies(request), META_override=self.get_meta(request)) 

Well, now you have a unique opportunity to look at the error 500, while experiencing joy - knowing that users are now more secure.

Source: https://habr.com/ru/post/164935/


All Articles