2

I'm using React and Django for my web application. As far as I know, Django uses a double submit pattern, where the CSRF token in the cookie and header/body are compared server side.

I use the following code to extract the CSRF token from document.cookie as specified by Django docs:

export function getToken() {
    let cookieValue = null;
    const name = "csrftoken";

    if (document.cookie && document.cookie !== "") {
        const cookies = document.cookie.split(";");
        for (var i = 0; i < cookies.length; i++) {
            const cookie = cookies[i].trim();
            if (cookie.substring(0, name.length + 1) === name + "=") {
                cookieValue = decodeURIComponent(
                    cookie.substring(name.length + 1)
                );
                break;
            }
        }
    }
    return cookieValue;
}

I then use it like so:

axios
    .post("/api/auth/login", 
    { username: username, password: password, },
    { withCredentials: true, headers: { "X-CSRFToken": getToken(), } }

So my question is, I've heard that an attacker can't access a user's cookies and thus can't extract the CSRF token to place in the request header/body. But why can't the attacker execute a script running the getToken function and place the result in a header like I did? (Say we ignore CORS for the sake of argument)

Example:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
    </head>
    <body>
        <p id="p">Hello!</p>
        <script>
            function getToken() {
                let cookieValue = null;
                const name = "csrftoken";

                if (document.cookie && document.cookie !== "") {
                    const cookies = document.cookie.split(";");
                    for (var i = 0; i < cookies.length; i++) {
                        const cookie = cookies[i].trim();
                        if (
                            cookie.substring(0, name.length + 1) ===
                            name + "="
                        ) {
                            cookieValue = decodeURIComponent(
                                cookie.substring(name.length + 1)
                            );
                            break;
                        }
                    }
                }
                return cookieValue;
            }

            document.getElementById("p").addEventListener("click", () => {
                fetch("http://localhost:8000/api/auth/logout", {
                    method: "POST",
                    headers: {
                        "X-CSRFToken": getToken(),
                    }
                })
            });
        </script>
    </body>
</html>
Sam Liu
  • 157
  • 1
  • 8
  • I think you're looking for point #4 in the docs: https://docs.djangoproject.com/en/3.2/ref/csrf/#how-it-works – schillingt Aug 05 '21 at 19:00
  • @schillingt Hmm. In that case, why would CSRF be an issue at all? The attacker's website will never be the same domain as the API, and so the same origin policy will stop the API call – Sam Liu Aug 05 '21 at 21:22
  • I don't understand your question. – schillingt Aug 05 '21 at 21:30
  • @schillingt Ok let me try again. Say an attacker's webpage looks like the HTML above and is on the domain, `evil.com`. From what I understand, point #4 states that the request won't go through because `evil.com` is a different domain than `localhost:8000`, and so the POST request is stopped by the same origin policy. How would an attacker be able to get around this same origin policy and perform a CSRF attack? – Sam Liu Aug 05 '21 at 21:36
  • If you're using all the security settings in that how it works section, you're protected against CSRF attacks from what I know. Unfortunately I don't have a great indepth knowledge of this topic so I'd refer you to OWASP: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html and https://owasp.org/www-community/attacks/csrf – schillingt Aug 06 '21 at 14:40

1 Answers1

0

It's fine to store the CSRF token in a cookie, because the attacker generally won't be able to run code in the user's browsing context. (Unless the attacker has injected code into the application, which is why the Content-Security-Policy header is critical.)

On the other hand, if, as you asked in a comment, an attacker creates a domain evil.com with your example code, the Same Origin Policy will stop the attacker from making the request to your API. But the Same Origin Policy will not stop the attacker from, e.g. making a form submission with method POST to your API. Hence the need for the CSRF token which proves to Django that the request comes from a legitimate user browsing context. This kind of CSRF attack can also be mitigated nowadays with the SameSite attribute on cookies, which can prevent modern browsers from sending sensitive cookies on any navigation (e.g. form submission) to another origin.

ffff
  • 81
  • 5