RSTR-CSRF-002 — Django @csrf_exempt on a state-changing view

Summary

A Django view is decorated with @csrf_exempt, removing the CSRF protection that the rest of the project's middleware enforces. If the view accepts POST / PUT / PATCH / DELETE, any cross-site form submission targeting it succeeds with the victim's session cookies attached.

Severity

Medium. The bug is real but limited to the single view; the rest of the application is still protected.

Languages

Python.

What rastray flags

Any use of the csrf_exempt decorator from django.views.decorators.csrf:

from django.views.decorators.csrf import csrf_exempt

@csrf_exempt                                          # ← flagged
def transfer_funds(request):
    ...
@csrf_exempt                                          # ← flagged
@require_POST
def webhook(request):
    ...

What rastray deliberately does not flag

  • Class-based views protected by Django's default CSRF middleware.
  • Views with @requires_csrf_token or @ensure_csrf_cookie.

How to fix it

For internal endpoints: remove @csrf_exempt. Let the middleware enforce the token; if the client is a SPA, expose the token via {% csrf_token %} or ensure_csrf_cookie and send it back as X-CSRFToken.

For third-party webhooks (Stripe, GitHub, Slack, etc.): verify the incoming signature instead of disabling CSRF. The signature does the job that the CSRF token does for browser-originated requests.

@csrf_exempt
@require_POST
def stripe_webhook(request):
    sig = request.META.get('HTTP_STRIPE_SIGNATURE', '')
    try:
        event = stripe.Webhook.construct_event(
            request.body, sig, settings.STRIPE_WEBHOOK_SECRET
        )
    except (ValueError, stripe.error.SignatureVerificationError):
        return HttpResponseBadRequest()
    ...

The csrf_exempt is unavoidable in that case — suppress it with a comment explaining the signature check substitutes for the missing CSRF protection:

# rastray-ignore: RSTR-CSRF-002 — Stripe webhook; signature verified above
@csrf_exempt
@require_POST
def stripe_webhook(request): ...

References