Why do we need both the csrftoken cookie and the HTML form's hidden input's csrfmiddlewaretoken value?
Asked Answered
S

4

27

I'm trying to learn about security. I am curious about why, in Django, when submitting a form (a POST), there are 2 separate elements that contain the same csrf token value:

  • The csrftoken cookie: {'csrftoken': '1effe96056e91a8f58461ad56c0d4ddc', ...

  • The form's hidden csrfmiddlewaretoken: <QueryDict: {u'csrfmiddlewaretoken': [u'1effe96056e91a8f58461ad56c0d4ddc'], ...

If Django is inserting the hidden csrf field/value to the form when it sends it to the browser (GET), and expects the same value back when receiving the POST, then why is it necessary to also set a cookie?

A more general question, if either of them was missing (form, cookie), could you provide a scenario that explains how this could be exploited (security attack)?

By the way, I ran a couple of simple tests to make sure that Django was checking the validity of each one separately and indeed it is:

  • If I change the form's csrf value before doing the POST, I get this debug error back:

    CSRF token missing or incorrect

  • If I delete the csrf cookie before doing the POST, I get a different error back:

    CSRF cookie not set.

I'm just familiar with basic csrf concepts and want to learn how django helps protect against these types of attacks.

Stepaniestepbrother answered 7/4, 2011 at 22:22 Comment(0)
S
23

From Jeff Atwood's blog entry:

Preventing CSRF and XSRF Attacks (Oct 14, 2008)

The original post

The Felten and Zeller paper (pdf) recommends the "double-submitted cookie" method to prevent XSRF:

When a user visits a site, the site should generate a (cryptographically strong) pseudorandom value and set it as a cookie on the user's machine. The site should require every form submission to include this pseudorandom value as a form value and also as a cookie value. When a POST request is sent to the site, the request should only be considered valid if the form value and the cookie value are the same. When an attacker submits a form on behalf of a user, he can only modify the values of the form. An attacker cannot read any data sent from the server or modify cookie values, per the same-origin policy. This means that while an attacker can send any value he wants with the form, he will be unable to modify or read the value stored in the cookie. Since the cookie value and the form value must be the same, the attacker will be unable to successfully submit a form unless he is able to guess the pseudorandom value.

The advantage of this approach is that it requires no server state; you simply set the cookie value once, then every HTTP POST checks to ensure that one of the submitted values contains the exact same cookie value. Any difference between the two means a possible XSRF attack.

Stepaniestepbrother answered 11/4, 2011 at 22:31 Comment(0)
F
8

The cookie is there for AJAX support. Quoting the Django docs:

While the above method can be used for AJAX POST requests, it has some inconveniences: you have to remember to pass the CSRF token in as POST data with every POST request. For this reason, there is an alternative method: on each XMLHttpRequest, set a custom X-CSRFToken header to the value of the CSRF token. This is often easier, because many javascript frameworks provide hooks that allow headers to be set on every request. In jQuery, you can use the ajaxSend event as follows:

$('html').ajaxSend(function(event, xhr, settings) {
    function getCookie(name) {
        var cookieValue = null;
        if (document.cookie && document.cookie != '') {
            var cookies = document.cookie.split(';');
            for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) == (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }
    if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
        // Only send the token to relative URLs i.e. locally.
        xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
    }
});

Adding this to a javascript file that is included on your site will ensure that AJAX POST requests that are made via jQuery will not be caught by the CSRF protection.

Frerichs answered 7/4, 2011 at 22:32 Comment(2)
Mike, thanks for the answer. I'm keeping the question open for a while to make sure that the only purpose of the csrf cookie is so that when submitting via Ajax, the header value can be copied from the cookie. Maybe there are additional, security related, reasons. Thanks.Stepaniestepbrother
I just remember this because AJAX requests didn't require CSRF until 1.2.5, and when we upgraded to 1.2.5 my AJAX code started breaking. It was a fun one to hunt down; it's not often that I have to break out curl to debug AJAX.Frerichs
O
6

They spot two different problems.

Cookie is to authenticate the client machine making the connection.

The hidden form field is to authenticate the source of the form.

Example Scenario: User A, on the client machine could bookmark the form. User B logs on, gets a valid cookie from today. User A could submit the invalid form field from yesterday when the browser has a left-over cookie from user B's session.


what client/browser resources are typically compromised,

None.

and how is it that these csrf fields help protect us from the forgery requests?

The CSRF tokens establish identity.

One (and only one) browser has a CSRF cookie token. But that browser could have multiple copies of a site open or bookmarked forms.

One (and only one) page form on that browser has a CSRF form token.

The browser and form cookies must match to assure one browser/one form.

Onder answered 7/4, 2011 at 22:28 Comment(3)
@jd: In your step 2, everything is not ok. The CSRF from the browser cookie (which identifies the client) doesn't match the CSRF in the form (which identifies the transaction in process).Onder
lott: will user B ever be able to share the same client/browser with user A? After user A logs-out (and has caused the csrf cookie to get set) and then user B logs-in and requests (GET) the form, won't the cookie get re-set, so that user B can send/POST his own form? That's what I tried to convey in step 2.Stepaniestepbrother
@jd: The point is to share either browser cookies (or form cookies). It happens all the time. Users do not log off (leaving browser cookies with CSRF tokens), they bookmark forms (with hidden CSRF tokens). The two situations are separate from each other and both of the two separate things (browser and form content) must be handled.Onder
K
0

This is a very old question, but I think it's important to point out the following.

In Django 5.0.6 (and possibly other previous Django versions), the csrftoken cookie and the csrfmiddleware value (of the HTML form's hidden input) are not the same. Apparently, this is to protect against BREACH attacks.

The csrfmiddleware is computed as a function of csrftoken and a randomly generated mask (in the get_token function), so the csrfmiddleware is different for each GET request, which means that, whenever you reload the page with the form, you will get a different csrfmiddleware, but you should still have the same csrftoken, although csrftoken can also expire. You can open your browser's developer tools and check for yourself.

An explanation of how CSRF protection works in Django can be found in the official documentation, although I found it confusing at the beginning, so you may want to look for other better explanations. The source code may also help you understand the details.

Karyolymph answered 29/6 at 10:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.