Skip to main content

Insecure session management - CSRF Fixation

Need

Secure session management and protection against CSRF Fixation

Context

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

Description

Non compliant code

from django.views import View
from django.http import JsonResponse
from .models import Transaction

class TransactionView(View):
def post(self, request, *args, **kwargs):
public_key = request.META.get('HTTP_AUTHORIZATION')
if not public_key:
return JsonResponse({'error': 'No authorization header'}, status=400)

# Fetch the transaction from the POST request
transaction_id = request.POST.get('transaction_id')
if not transaction_id:
return JsonResponse({'error': 'No transaction ID'}, status=400)
try:
transaction = Transaction.objects.get(id=transaction_id)
except Transaction.DoesNotExist:
return JsonResponse({'error': 'Invalid transaction ID'}, status=400)

# Validate the public key
if transaction.public_key != public_key:
return JsonResponse({'error': 'Invalid public key'}, status=403)

# Process the transaction
transaction.process()
return JsonResponse({'status': 'success'}, status=200)

The above code represents a view in Django that handles POST requests for processing transactions.

The vulnerability lies in the session management where the public key is used as the authorization header and remains the same for all payment links. This makes it susceptible to Cross-Site Request Forgery (CSRF) Fixation attacks.

In this scenario, an attacker can create a button with the content of a request and trick a user into running a transaction. The app would then receive a push notification and complete the request, allowing the attacker to execute transactions on behalf of the user without their knowledge or consent.

The TransactionView class handles the POST request for a transaction. It first checks if the 'Authorization' header is present in the request. If it's not, it returns a 400 status code with an error message. Then, it fetches the transaction ID from the POST request and checks if it's present. If it's not, it returns a 400 status code with an error message.

Then, it tries to fetch the transaction from the database using the provided transaction ID. If the transaction does not exist, it returns a 400 status code with an error message.

Next, it validates the public key by comparing it with the transaction's public key. If they don't match, it returns a 403 status code with an error message.

Finally, if all checks pass, it processes the transaction and returns a 200 status code with a success message. This is where the CSRF Fixation can occur, as an attacker could potentially manipulate the request to use their own public key, thereby gaining unauthorized access to the transaction.

Steps

  • Implement CSRF protection middleware in your Django application. This middleware should be used to validate that every POST request comes with a CSRF token that matches the one stored in the user's session.
  • Generate a new CSRF token for each session. This token should be unique and unpredictable, making it difficult for an attacker to guess.
  • Include the CSRF token in every form that targets an internal URL. This ensures that the user's browser includes the CSRF token in its request, allowing the server to verify it.
  • Check the CSRF token in the server-side code. If the token is missing or doesn't match the one stored in the session, reject the request.
  • Rotate CSRF tokens after the user authenticates. This ensures that the token is tied to the session and the user, not just the browser.
  • Ensure that the public key is not predictable or easily guessable. It should be unique for each user and should change regularly.
  • Consider using a secure and updated library or framework that provides built-in protections against CSRF attacks.

Compliant code

from django.views import View
from django.http import JsonResponse
from django.middleware.csrf import CsrfViewMiddleware
from .models import Transaction

class TransactionView(View):
def post(self, request, *args, **kwargs):
# CSRF protection
csrf_middleware = CsrfViewMiddleware()
csrf_middleware.process_view(request, None, (), {})

public_key = request.META.get('HTTP_AUTHORIZATION')
if not public_key:
return JsonResponse({'error': 'No authorization header'}, status=400)

# Fetch the transaction from the POST request
transaction_id = request.POST.get('transaction_id')
if not transaction_id:
return JsonResponse({'error': 'No transaction ID'}, status=400)
try:
transaction = Transaction.objects.get(id=transaction_id)
except Transaction.DoesNotExist:
return JsonResponse({'error': 'Invalid transaction ID'}, status=400)

# Validate the public key
if transaction.public_key != public_key:
return JsonResponse({'error': 'Invalid public key'}, status=403)

# Process the transaction
transaction.process()
return JsonResponse({'status': 'success'}, status=200)

The updated code includes Django's built-in CSRF protection middleware. This middleware is used to validate that every POST request comes with a CSRF token that matches the one stored in the user's session.

The CsrfViewMiddleware is instantiated and its process_view method is called at the beginning of the post method. This method checks the CSRF token in the request against the one stored in the session. If the tokens do not match or if the token is not present in the request, a 403 Forbidden response is returned.

The public key is still fetched from the HTTP_AUTHORIZATION header and validated against the public key stored in the transaction. However, the CSRF token check ensures that the request is a legitimate request from the user, not a CSRF attack.

The transaction is processed only if the CSRF token and public key checks pass. If the transaction is processed successfully, a JSON response with a status of 'success' is returned. If any of the checks fail, an error message is returned in the JSON response.

This updated code provides protection against CSRF attacks by ensuring that every POST request is accompanied by a valid CSRF token. This makes it much more difficult for an attacker to trick a user into performing a transaction without their knowledge or consent.

References