Django CsrfViewMiddleware and exploited SubDomain

Context (Double Submit Cookie and Subdomains):

When using a CSRF token with a cookie for the Double Submit Cookie method, you have to ensure the client receiving the cookie can read this cookie, and then add the CSRFToken from it into the headers of future requests to the backend.

When building a frontend that's on a different (sub)domain than the backend, you have to specify the domain attribute for the cookie. For example, if the frontend is on app.example.com, while the backend is on api.example.com, then the domain for the cookie must be set to example.com, or else app.example.com will not be able to read CSRFToken from the cookie.

However, this opens a vulnerability to exploited subdomains. This presentation from OWASP explains how that works. From what I understand, since we allow subdomains of example.com to read the cookie, then a malicious site like evil.example.com can be set-up to falsely set the CSRF cookie, bypassing the CSRF protection from the Double Submit Cookie method.

Question

Django may have a protection for this but I can hardly find any documentation on it. Specifically, it has this CSRF_TRUSTED_ORIGINS used by the CsrfViewMiddleware. This is what the docs say: CSRF_TRUSTED_ORIGINS

A list of trusted origins for unsafe requests (e.g. POST).

For requests that include the Origin header, Django’s CSRF protection requires that header match the origin present in the Host header.

For a secure unsafe request that doesn’t include the Origin header, the request must have a Referer header that matches the origin present in the Host header.

These checks prevent, for example, a POST request from subdomain.example.com from succeeding against api.example.com. If you need cross-origin unsafe requests, continuing the example, add 'https://subdomain.example.com' to this list (and/or http://... if requests originate from an insecure page).

The setting also supports subdomains, so you could add 'https://*.example.com', for example, to allow access from all subdomains of example.com.

And the implementation in django.middleware paraphrased is like this:

class CsrfViewMiddleware(MiddlewareMixin):
    def _check_referer(self, request):
       referer = request.META.get("HTTP_REFERER")
        if referer is None:
            raise RejectRequest(REASON_NO_REFERER)

        try:
            referer = urlparse(referer)
        except ValueError:
            raise RejectRequest(REASON_MALFORMED_REFERER)

        # Make sure we have a valid URL for Referer.
        if "" in (referer.scheme, referer.netloc):
            raise RejectRequest(REASON_MALFORMED_REFERER)

        # Ensure that our Referer is also secure.
        if referer.scheme != "https":
            raise RejectRequest(REASON_INSECURE_REFERER)

        if any(
            is_same_domain(referer.netloc, host)
            for host in self.csrf_trusted_origins_hosts
        ):
            # Passes _check_referer
            return

    @cached_property
    def csrf_trusted_origins_hosts(self):
        return [
            urlparse(origin).netloc.lstrip("*")
            for origin in settings.CSRF_TRUSTED_ORIGINS
        ]

Is including the frontend's domain in CSRF_TRUSTED_ORIGINS enough to defend against someone trying to exploit with a subdomain?

OWASP explains a "Signed Double-Submit Cookie" method, but this adds a lot of extra implementation for me because of how I set up authentication. Ideally, I want to just rely on what django has already implemented for CSRF protection if it is enough.

Вернуться на верх