835

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?

pkamb
  • 33,281
  • 23
  • 160
  • 191
Shawn
  • 8,381
  • 3
  • 16
  • 8
  • 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: http://en.wikipedia.org/wiki/Cross-site_request_forgery – Robert Harvey Mar 06 '11 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 '18 at 18:08
  • 3
    From [OWASP Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet](https://www.owasp.org/index.php/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 '19 at 13:52
  • 2
    This is a very good video example about it: https://www.youtube.com/watch?v=hW2ONyxAySY tl;dw: CSRF tokens make the requests input unpredictable a priori. Thus, an attacker can't easily reproduce it. – floatingpurr Nov 10 '21 at 13:04

5 Answers5

1859

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.

FreelanceConsultant
  • 13,167
  • 27
  • 115
  • 225
Lutz Prechelt
  • 36,608
  • 11
  • 63
  • 88
  • 58
    And obviously the token would ideally be named *anti*-CSRF token, but the name is probably complicated enough as it is. – Lutz Prechelt May 27 '16 at 19:15
  • 8
    @LutzPrechelt thank you. why can't javascript be able to obtain any authenticity tokens from the browser? – BenKoshy Aug 10 '16 at 06:27
  • 102
    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 05 '16 at 14:45
  • Dear @LutzPrechelt Can you please following points: 1 - How to fake site finds the request URL pattern ? you mentioned easy. 2 - If they change the values of URL request, their scenario should still work or not ? for example amount = 2000 . if not , why ? Thanks in advance. – Danial Dec 14 '16 at 14:36
  • "correctly guesses you are logged into mybank.com (requires some luck!)," — It doesn't need luck: They could brute force lots of online banks, and get lots of visitors. They only need to be lucky occasionally. – Quentin Dec 27 '16 at 11:39
  • @Danial — 1. By using the site and looking at how it works. 2. Well yes, but its a hypothetical example anyway. – Quentin Dec 27 '16 at 11:40
  • 5
    What prevents `www.cute-cat-pictures.org` from crawling `www.mybank.com` and get the CSRF token there? I mean, isn't it useful only for pages behind a login? – Augustin Riedinger Oct 02 '17 at 12:06
  • 3
    @AugustinRiedinger: The crawled page would have a different CSRF token than the CSRF token served on the page of the user intending to transfer the amount. – Madeyedexter Oct 03 '17 at 08:25
  • 3
    How can a webpage in modern browser "guess" what other tabs you have opened, and how can quess you are logged into that pages? Via javascript I think it's impossible. Let's assume all websites have strict Same origin policy. Does CSRF attack presume, you have vulnerable or infected browser and / or computer? – Mitja Gustin Nov 22 '17 at 09:41
  • 1
    @ MitjaGustin: The guessing is just assuming. Most often the assumption fails and the fake request does not work out, sometimes it is correct and the attack (without CSRF) succeeds. – Lutz Prechelt Nov 27 '17 at 08:58
  • So if I allow only www.mybank.com in `Access-Control-Allow-Origin` , I don't have to worry about csrf token? – Sumeet Feb 07 '18 at 13:45
  • 11
    @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 08 '18 at 06:15
  • 19
    @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 08 '18 at 06:30
  • In your `You retrieved that www.cute-cat-pictures.org page, so your browser will make that request.`, do you mean from the `mybank.com` page to view a link to the `www.cute-cat-pictures.org` page? the link is in the `mybank.com`? – aircraft Mar 15 '18 at 14:56
  • 3
    @MitjaGustin The guessing works this this: "Hmm, `mybank.com` is the biggest bank, so out of every 10 000 people visiting my `cute-cat-pictures`, about 500 will be `mybank` customers -- and if I'm a little lucky, 5 of them will be logged in to `mybank` at that time. Let's see..." – Lutz Prechelt Mar 16 '18 at 09:35
  • 1
    If a HttpGet request can modify the resource's state, csrf token stored in cookie won't do any help right? – ROROROOROROR May 29 '18 at 06:44
  • Just pointing out that the number is not random and impossible to guess. In fact take any circle and divide perimeter by diameter :-) – Tal-Botvinnik Jun 12 '18 at 23:04
  • Are you the author of this post? the content is the same: https://cloudunder.io/blog/csrf-token – boctulus Jan 16 '19 at 19:38
  • 1
    i like this `just because they can't use the API from another website.` – shyammakwana.me Mar 12 '19 at 13:32
  • @boctulus, I think you meant to post your comment on Dan's answer, [below](https://stackoverflow.com/a/49435939/3469273)? – mfluehr Jul 25 '19 at 15:52
  • 1
    So is the correct setting of Access-Control-Allow-Origin an alternative of including the CSRF-token? – Ekaterina Jun 12 '20 at 11:50
  • Can mybank verify the origin header value to deny the request ? Origin value is set by browsers and in this case should be cute-cat-pictures.org – Frank Q. Jan 12 '21 at 19:02
  • I didn't understand, the browser will automatically send the cookie of `www.mybank.com` in an request made in `www.cute-cat-pictures.org`? – Leto Apr 22 '21 at 11:19
  • 1
    @Ekaterina On the contrary Access-Control-Allow-Origin must be restricted for CSRF protection to actually work. Otherwise the malicious site could make a request to mybank and read the CSRF token in the response and then use it to conduct the fraud – kaan_a Aug 04 '21 at 07:10
  • 1
    @Leto Just adding to the clarification: cute-cat-pictures.org never gets to SEE your cookies, but it doesn't need to. It can force your browser to make a request to mybank.com, no different from a page opening a new tab when you click a link. This request can happen in the background. When your browser makes ANY request to mybank.com, it will automatically send any mybank cookies along with it. If that cookie includes a still valid login session, then the request can include malicious instructions. CSRF protection is a two-part paradigm involving the tokens AND restricted origins in HTTP. – Austin Oct 02 '21 at 10:09
  • @Austin In the scenario of making a request to mybank.com from cute-cat-pictures.org, what will the mybank.com server see as the Origin header of the request? – Orestis Kapar Feb 08 '22 at 09:37
  • What can't wrap my head around is: if the browser willingly sends all the `www.mybank.com` cookies to the bank server, why wouldn't that just happen for the crsf-token as well? I'ld expect it to be send altogether with the session data. – NotX Feb 23 '22 at 10:49
  • 1
    @NotX The CSRF token is part of the form data (this is a `POST` request), not in a cookie. The attacker produces all form data without help from the `www.mybank.com` server and so lacks the CSRF token. – Lutz Prechelt Mar 09 '22 at 08:58
  • @Marcel RE: "I think it is not enough that the token is always different..." true, which is why many sites require the token to be sent in a custom header. Then, a malicious website can't send it due to cross-origin custom header restriction. – java-addict301 Dec 14 '22 at 02:51
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.

Dmitry Shevchenko
  • 31,814
  • 10
  • 56
  • 62
  • @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 '13 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 '13 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 – Dmitry Shevchenko Jan 11 '13 at 02: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? – A F Aug 13 '14 at 22:31
  • 7
    Well, suppose I inject a malicious iframe of "http://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. – Dmitry Shevchenko Aug 14 '14 at 06:34
94

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.

Dan
  • 1,077
  • 7
  • 6
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.

tkone
  • 22,092
  • 5
  • 54
  • 78
  • 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? – Jack Marchetti Apr 23 '14 at 00: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 '14 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? – Paul Preibisch May 05 '14 at 07: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 07 '14 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. – Pedro Cordeiro Oct 27 '15 at 16:18
  • Just forcing the attacker to make an additional request to fetch the token would not provide much protection really. The actual protection results from properly configured access control, which prevents the page (and token) to be retrieved from another domain. [This comment](https://stackoverflow.com/questions/5207160/what-is-a-csrf-token-what-is-its-importance-and-how-does-it-work#comment68126014_33829607) explains it well. – Oliver Sieweke Apr 19 '22 at 10:15
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.

gladysbixly
  • 2,591
  • 18
  • 15
  • 11
    Do not rely on the referer header, it can easily be faked. – kag May 29 '15 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 08 '15 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 07 '16 at 16:10