What is a CSRF token? What is its importance and how does it work?

I am writing an application (Django, it so happens) and I just want an idea of what actually a “CSRF token” is and how it protects the data.

Is the post data not safe if you do not use CSRF tokens?

  • 18

    It’s a secret, user-specific token in all form submissions and side-effect URLs to prevent Cross-Site Request Forgeries. More info here: en.wikipedia.org/wiki/Cross-site_request_forgery

    – 

  • 4

    seems like there is a fine line between protecting a question and banning it for being too broad 😀

    – 

  • 3

    From OWASP Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet: “Cross-Site Scripting is not necessary for CSRF to work. However, any cross-site scripting vulnerability can be used to defeat all CSRF mitigation techniques […].This is because an XSS payload can simply read any page on the site using an XMLHttpRequest […]. It is imperative that no XSS vulnerabilities are present to ensure that CSRF defenses can’t be circumvented.

    – 




  • 2

    This is a very good video example about it: youtube.com/watch?v=hW2ONyxAySY tl;dw: CSRF tokens make the requests input unpredictable a priori. Thus, an attacker can’t easily reproduce it.

    – 

Cross-Site Request Forgery (CSRF) in simple words

  • Assume you are currently logged into your online banking at www.mybank.com
  • Assume a money transfer from mybank.com will result in a request of (conceptually) the form http://www.mybank.com/transfer?to=<SomeAccountnumber>;amount=<SomeAmount>. (Your account number is not needed, because it is implied by your login.)
  • You visit www.cute-cat-pictures.org, not knowing that it is a malicious site.
  • If the owner of that site knows the form of the above request (easy!) and correctly guesses you are logged into mybank.com (requires some luck!), they could include on their page a request like http://www.mybank.com/transfer?to=123456;amount=10000 (where 123456 is the number of their Cayman Islands account and 10000 is an amount that you previously thought you were glad to possess).
  • You retrieved that www.cute-cat-pictures.org page, so your browser will make that request.
  • Your bank cannot recognize this origin of the request: Your web browser will send the request along with your www.mybank.com cookie and it will look perfectly legitimate. There goes your money!

This is the world without CSRF tokens.

Now for the better one with CSRF tokens:

  • The transfer request is extended with a third argument: http://www.mybank.com/transfer?to=123456;amount=10000;token=31415926535897932384626433832795028841971.
  • That token is a huge, impossible-to-guess random number that mybank.com will include on their own web page when they serve it to you. It is different each time they serve any page to anybody.
  • The attacker is not able to guess the token, is not able to convince your web browser to surrender it (if the browser works correctly…), and so the attacker will not be able to create a valid request, because requests with the wrong token (or no token) will be refused by www.mybank.com.

Result: You keep your 10000 monetary units.

(Your mileage may vary.)

EDIT from comment worth reading by SOFe:

It would be worthy to note that script from www.cute-cat-pictures.org normally does not have access to your anti-CSRF token from www.mybank.com because of HTTP access control. This note is important for some people who unreasonably send a header Access-Control-Allow-Origin: * for every website response without knowing what it is for, just because they can’t use the API from another website.

Yes, the post data is safe. But the origin of that data is not. This way somebody can trick user with JS into logging in to your site, while browsing attacker’s web page.

In order to prevent that, django will send a random key both in cookie, and form data.
Then, when users POSTs, it will check if two keys are identical. In case where user is tricked, 3rd party website cannot get your site’s cookies, thus causing auth error.

The Cloud Under blog has a good explanation of CSRF tokens. (archived)

Imagine you had a website like a simplified Twitter, hosted on a.com.
Signed in users can enter some text (a tweet) into a form that’s being
sent to the server as a POST request and published when they hit the
submit button. On the server the user is identified by a cookie
containing their unique session ID, so your server knows who posted
the Tweet.

The form could be as simple as that:

 <form action="http://a.com/tweet" method="POST">
   <input type="text" name="tweet">
   <input type="submit">
 </form> 

Now imagine, a bad guy copies and pastes this form to his malicious
website, let’s say b.com. The form would still work. As long
as a user is signed in to your Twitter (i.e. they’ve got a valid
session cookie for a.com), the POST request would be sent to
http://a.com/tweet and processed as usual when the user clicks the
submit button.

So far this is not a big issue as long as the user is made aware about
what the form exactly does, but what if our bad guy tweaks the form
like this:

 <form action="https://example.com/tweet" method="POST">
   <input type="hidden" name="tweet" value="Buy great products at http://b.com/#iambad">
   <input type="submit" value="Click to win!">
 </form> 

Now, if one of your users ends up on the bad guy’s website and hits
the “Click to win!” button, the form is submitted to
your website, the user is correctly identified by the session ID in
the cookie and the hidden Tweet gets published.

If our bad guy was even worse, he would make the innocent user submit
this form as soon they open his web page using JavaScript, maybe even
completely hidden away in an invisible iframe. This basically is
cross-site request forgery.

A form can easily be submitted from everywhere to everywhere.
Generally that’s a common feature, but there are many more cases where
it’s important to only allow a form being submitted from the domain
where it belongs to.

Things are even worse if your web application doesn’t distinguish
between POST and GET requests (e.g. in PHP by using $_REQUEST instead
of $_POST). Don’t do that! Data altering requests could be submitted
as easy as <img src="http://a.com/tweet?tweet=This+is+really+bad">,
embedded in a malicious website or even an email.

How do I make sure a form can only be submitted from my own website?
This is where the CSRF token comes in. A CSRF token is a random,
hard-to-guess string. On a page with a form you want to protect, the
server would generate a random string, the CSRF token, add it to the
form as a hidden field and also remember it somehow, either by storing
it in the session or by setting a cookie containing the value. Now the
form would look like this:

    <form action="https://example.com/tweet" method="POST">
      <input type="hidden" name="csrf-token" value="nc98P987bcpncYhoadjoiydc9ajDlcn">
      <input type="text" name="tweet">
      <input type="submit">
    </form> 

When the user submits the form, the server simply has to compare the
value of the posted field csrf-token (the name doesn’t
matter) with the CSRF token remembered by the server. If both strings
are equal, the server may continue to process the form. Otherwise the
server should immediately stop processing the form and respond with an
error.

Why does this work? There are several reasons why the bad guy from our
example above is unable to obtain the CSRF token:

Copying the static source code from our page to a different website
would be useless, because the value of the hidden field changes with
each user. Without the bad guy’s website knowing the current user’s
CSRF token your server would always reject the POST request.

Because the bad guy’s malicious page is loaded by your user’s browser
from a different domain (b.com instead of a.com), the bad guy has no
chance to code a JavaScript, that loads the content and therefore our
user’s current CSRF token from your website. That is because web
browsers don’t allow cross-domain AJAX requests by default.

The bad guy is also unable to access the cookie set by your server,
because the domains wouldn’t match.

When should I protect against cross-site request forgery? If you can
ensure that you don’t mix up GET, POST and other request methods as
described above, a good start would be to protect all POST requests by
default.

You don’t have to protect PUT and DELETE requests, because as
explained above, a standard HTML form cannot be submitted by a browser
using those methods.

JavaScript on the other hand can indeed make other types of requests,
e.g. using jQuery’s $.ajax() function, but remember, for AJAX requests
to work the domains must match (as long as you don’t explicitly
configure your web server otherwise).

This means, often you do not even have to add a CSRF token to AJAX
requests, even if they are POST requests, but you will have to make
sure that you only bypass the CSRF check in your web application if
the POST request is actually an AJAX request. You can do that by
looking for the presence of a header like X-Requested-With, which AJAX
requests usually include. You could also set another custom header and
check for its presence on the server side. That’s safe, because a
browser would not add custom headers to a regular HTML form submission
(see above), so no chance for Mr Bad Guy to simulate this behaviour
with a form.

If you’re in doubt about AJAX requests, because for some reason you
cannot check for a header like X-Requested-With, simply pass the
generated CSRF token to your JavaScript and add the token to the AJAX
request. There are several ways of doing this; either add it to the
payload just like a regular HTML form would, or add a custom header to
the AJAX request. As long as your server knows where to look for it in
an incoming request and is able to compare it to the original value it
remembers from the session or cookie, you’re sorted.

The site generates a unique token when it makes the form page. This token is required to post/get data back to the server.

Since the token is generated by your site and provided only when the page with the form is generated, some other site can’t mimic your forms — they won’t have the token and therefore can’t post to your site.

Leave a Comment