Cross-site request forgery - Python

Cross-site request forgery - Python

Need

Prevention of unauthorized actions executed on behalf of authenticated users

Context

  • Usage of Python 3.0+ for developing Python applications
  • Usage of Django for building web applications

Description

Non compliant code

        from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt
from .models import UserProfile

@csrf_exempt
def update_profile(request):
    if request.method == 'POST':
        user_profile = UserProfile.objects.get(user=request.user)
        user_profile.bio = request.POST.get('bio')
        user_profile.save()
    return render(request, 'update_profile.html')
        
        

The above code is a simple Django view function that updates a user's profile. The update_profile function is decorated with @csrf_exempt which disables Django's built-in CSRF protection for this view.

In this function, it first checks if the request method is 'POST'. If it is, it retrieves the UserProfile object for the currently authenticated user, updates the 'bio' field with the data sent in the POST request, and then saves the changes to the database.

The vulnerability here is that because CSRF protection is disabled, an attacker could trick an authenticated user into submitting a POST request to this endpoint without their knowledge or consent. This could lead to unauthorized changes to the user's profile. For example, an attacker could create a malicious website that sends a POST request to this endpoint when visited by an authenticated user. The user's browser would automatically include their session cookie with the request, so the request would be authenticated and the profile update would be successful.

Steps

  • Remove the @csrf_exempt decorator from the update_profile view. This decorator disables CSRF protection for this view, which is not recommended.
  • Ensure that Django's CSRF middleware is enabled. This is typically done in your settings file.
  • In your HTML form, include a CSRF token. This can be done by adding `{% csrf_token %}` inside your form tags.
  • Ensure that the form is submitted using a POST request. CSRF protection is not applied to GET requests.
  • Make sure that the user is authenticated before performing any actions. This can be done by using Django's authentication system.

Compliant code

        from django.shortcuts import render
from .models import UserProfile
from django.contrib.auth.decorators import login_required

@login_required
def update_profile(request):
    if request.method == 'POST':
        user_profile = UserProfile.objects.get(user=request.user)
        user_profile.bio = request.POST.get('bio')
        user_profile.save()
    return render(request, 'update_profile.html')
        
        

The updated code removes the @csrf_exempt decorator from the update_profile view. This decorator was disabling CSRF protection for this view, which is not recommended as it opens up the application to Cross-Site Request Forgery attacks.

In the updated code, we have also added the @login_required decorator. This ensures that the user is authenticated before they can update their profile. If a user who is not authenticated tries to access this view, they will be redirected to the login page.

In your HTML form, you should include a CSRF token. This can be done by adding {% csrf_token %} inside your form tags. This ensures that the form can't be submitted by a malicious site because the CSRF token is required and can't be guessed.

Ensure that Django's CSRF middleware is enabled. This is typically done in your settings file. If it's not enabled, enable it by adding 'django.middleware.csrf.CsrfViewMiddleware' to your MIDDLEWARE settings.

Also, ensure that the form is submitted using a POST request. CSRF protection is not applied to GET requests. In the given code, we are checking if the request method is POST before processing the form data.

By following these steps, you can protect your Django application from CSRF attacks.

References