855

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?

4
  • 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 Mar 6, 2011 at 22:53
  • 4
    seems like there is a fine line between protecting a question and banning it for being too broad :D
    – anton1980
    Oct 18, 2018 at 18:08
  • 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."
    – toraritte
    Jan 25, 2019 at 13:52
  • 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. Nov 10, 2021 at 13:04

5 Answers 5

1905

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.

29
  • 66
    And obviously the token would ideally be named anti-CSRF token, but the name is probably complicated enough as it is. May 27, 2016 at 19:15
  • 8
    @LutzPrechelt thank you. why can't javascript be able to obtain any authenticity tokens from the browser?
    – BenKoshy
    Aug 10, 2016 at 6:27
  • 109
    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.
    – SOFe
    Nov 5, 2016 at 14:45
  • 13
    @AugustinRiedinger If the attacker opens the webpage on his computer - since they do not have the cookie of the logged in user - they will not receive the corresponding csrf token (each csrf token should be valid only for specific user session). If the attacker tries to load the webpage containing the token on the computer of the user, with a script placed in cute-cat-pictures website, browser will prevent him to read the www.mybank.com (and the token) because of the same origin policy.
    – Marcel
    Feb 8, 2018 at 6:15
  • 21
    @LutzPrechelt I think it is not enough that the token is always different, it has to be paired with a session and the server has to check that the token it receives was generated for a session which the server identifies by the received cookie. Otherwise, the hacker can just visit mybank themselves and get some valid token. So if you use a new token with every form you have to save it paired with the sessionid on the server. It is probably easier to use the same token per session.
    – Marcel
    Feb 8, 2018 at 6:30
233

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.

5
  • 1
    @DmitryShevchenko Hi, trying to understand how is this method of cookie+form-input different from just validating the referrer on the server side? All the examples that I find is related to a hacker tricking the user to post from his site to the actual site.
    – Ethan
    Jan 10, 2013 at 14:36
  • Ok, I found out why the referrer is not used. It's blocked in many cases as it's considered to hold sensitive information sometimes. Corporates and their proxies typically do that. However, if HTTPS is used, then there is more likelihood that it will not be blocked.
    – Ethan
    Jan 10, 2013 at 21:26
  • 5
    It's easy to change referrer, I would not say that it is a reliable piece of information. CSRF token, however, is generated using server secret key and usually tied to the user Jan 11, 2013 at 2:05
  • 1
    I don't really understand why this is a security threat. The user will be logged into another site... but the original site won't have any way to retrieve that information. Right?
    – Akhil F
    Aug 13, 2014 at 22:31
  • 7
    Well, suppose I inject a malicious iframe of "bank.com/transfer?from=x&to=y" in a, say, Facebook.com. If you're customer of bank.com and you go to Facebook, that iframe will load bank page, with your cookies (because browser will send them along to a known domain) and make a money transfer. Without you knowing anything. Aug 14, 2014 at 6:34
98

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.

7
  • Thanks for the detailed info. During the post request , the site has to send the csrf token to the server, so when will the client send this csrf token to the server? Is it while making the preflight options request? Please elablorate on this part.. Nov 30, 2018 at 10:25
  • 4
    @Dan How come b.com can access the cookies of another site a.com?
    – zakir
    Feb 9, 2019 at 21:13
  • 1
    great explanation! Jan 17, 2022 at 15:01
  • But where does one get the token? Which request usually returns it?
    – mathtick
    Aug 16, 2022 at 16:21
  • Nice explanation. I have one question:- So CSRF token is valid per request or per user and changes after each request ?
    – Ali Abbas
    Sep 17, 2022 at 6:50
86

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.

6
  • 14
    Could a user grab the token output within the source, grab the cookie sent to them and then from a 3rd party site submit? Apr 23, 2014 at 0:51
  • 9
    @JackMarchetti yes. but it would be costly since every time you wanted to submit the form from a 3rd party site you'd have to load the page and parse out the token. CSRF tokens should be ideally coupled with other forms of security if you're concerned with this vector of attack
    – tkone
    Apr 23, 2014 at 11:36
  • 4
    I have the same question as @JackMarchetti, whats not clear is - if the CSRF token changes on each login. If it stays the same, what would prevent an attacker from first logging in, grabbing the request token, and then inserting that token in the attack? May 5, 2014 at 7:28
  • 7
    @PaulPreibisch it should change on each page load - not on each login. This way the attacker would have to request the page each time they wanted to submit the form. Makes it much more difficult.
    – tkone
    May 7, 2014 at 23:31
  • 14
    @tkone, It doesn't really make it much more difficult. If just doubles the amount of effort and time. It doesn't add any kind of prohibitive processing. The trick is also associating the CSRF token to a domain-specific cookie, and sending this cookie along with the form. Both the cookie and the form post data would have to be sent to the server on the POST request. This way would require a Cookie-Hijacking attack to be able to emulate a legitimate request. Oct 27, 2015 at 16:18
11

The root of it all is to make sure that the requests are coming from the actual users of the site. A csrf token is generated for the forms and Must be tied to the user's sessions. It is used to send requests to the server, in which the token validates them. This is one way of protecting against csrf, another would be checking the referrer header.

3
  • 12
    Do not rely on the referer header, it can easily be faked.
    – kag
    May 29, 2015 at 18:06
  • 4
    This is the correct answer! The token MUST be tied to a session on the server. Comparing Cookie + Form data like the most up voted answer suggests is completely wrong. These components both form part of the request, which the client constructs.
    – Lee Davis
    Sep 8, 2015 at 16:04
  • 3
    Actually, no. The token MUST be tied to each REQUEST to the Server. If you only tie it to the session, then you run the risk of someone stealing the session's token and submitting a request with that token. So for max safety the token must be tied to each http requiest.
    – chrisl08
    Apr 7, 2016 at 16:10

Not the answer you're looking for? Browse other questions tagged or ask your own question.