338

I'm trying to understand the whole issue with CSRF and appropriate ways to prevent it. (Resources I've read, understand, and agree with: OWASP CSRF Prevention CHeat Sheet, Questions about CSRF.)

As I understand it, the vulnerability around CSRF is introduced by the assumption that (from the webserver's point of view) a valid session cookie in an incoming HTTP request reflects the wishes of an authenticated user. But all cookies for the origin domain are magically attached to the request by the browser, so really all the server can infer from the presence of a valid session cookie in a request is that the request comes from a browser which has an authenticated session; it cannot further assume anything about the code running in that browser, or whether it really reflects user wishes. The way to prevent this is to include additional authentication information (the "CSRF token") in the request, carried by some means other than the browser's automatic cookie handling. Loosely speaking, then, the session cookie authenticates the user/browser and the CSRF token authenticates the code running in the browser.

So in a nutshell, if you're using a session cookie to authenticate users of your web application, you should also add a CSRF token to each response, and require a matching CSRF token in each (mutating) request. The CSRF token then makes a roundtrip from server to browser back to server, proving to the server that the page making the request is approved by (generated by, even) that server.

On to my question, which is about the specific transport method used for that CSRF token on that roundtrip.

It seems common (e.g. in AngularJS, Django, Rails) to send the CSRF token from server to client as a cookie (i.e. in a Set-Cookie header), and then have Javascript in the client scrape it out of the cookie and attach it as a separate XSRF-TOKEN header to send back to the server.

(An alternate method is the one recommended by e.g. Express, where the CSRF token generated by the server is included in the response body via server-side template expansion, attached directly to the code/markup that will supply it back to the server, e.g. as a hidden form input. That example is a more web 1.0-ish way of doing things, but would generalize fine to a more JS-heavy client.)

Why is it so common to use Set-Cookie as the downstream transport for the CSRF token / why is this a good idea? I imagine the authors of all these frameworks considered their options carefully and didn't get this wrong. But at first glance, using cookies to work around what's essentially a design limitation on cookies seems daft. In fact, if you used cookies as the roundtrip transport (Set-Cookie: header downstream for the server to tell the browser the CSRF token, and Cookie: header upstream for the browser to return it to the server) you would reintroduce the vulnerability you are trying to fix.

I realize that the frameworks above don't use cookies for the whole roundtrip for the CSRF token; they use Set-Cookie downstream, then something else (e.g. a X-CSRF-Token header) upstream, and this does close off the vulnerability. But even using Set-Cookie as the downstream transport is potentially misleading and dangerous; the browser will now attach the CSRF token to every request including genuine malicious XSRF requests; at best that makes the request bigger than it needs to be and at worst some well-meaning but misguided piece of server code might actually try to use it, which would be really bad. And further, since the actual intended recipient of the CSRF token is client-side Javascript, that means this cookie can't be protected with http-only. So sending the CSRF token downstream in a Set-Cookie header seems pretty suboptimal to me.

Community
  • 1
  • 1
metamatt
  • 12,399
  • 7
  • 42
  • 55
  • 1
    It's a great question hitting the right spot. – kta Mar 23 '20 at 08:44
  • 3
    More curious yet is that OWASP states "CSRF tokens should not be transmitted using cookies." https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html – java-addict301 Sep 04 '20 at 16:04
  • Hm why would CSRF be an issue if the cookie has SameSite on it? – Dominic May 17 '21 at 06:37

4 Answers4

304

A good reason, which you have sort of touched on, is that once the CSRF cookie has been received, it is then available for use throughout the application in client script for use in both regular forms and AJAX POSTs. This will make sense in a JavaScript heavy application such as one employed by AngularJS (using AngularJS doesn't require that the application will be a single page app, so it would be useful where state needs to flow between different page requests where the CSRF value cannot normally persist in the browser).

Consider the following scenarios and processes in a typical application for some pros and cons of each approach you describe. These are based on the Synchronizer Token Pattern.

Request Body Approach

  1. User successfully logs in.
  2. Server issues auth cookie.
  3. User clicks to navigate to a form.
  4. If not yet generated for this session, server generates CSRF token, stores it against the user session and outputs it to a hidden field.
  5. User submits form.
  6. Server checks hidden field matches session stored token.

Advantages:

  • Simple to implement.
  • Works with AJAX.
  • Works with forms.
  • Cookie can actually be HTTP Only.

Disadvantages:

  • All forms must output the hidden field in HTML.
  • Any AJAX POSTs must also include the value.
  • The page must know in advance that it requires the CSRF token so it can include it in the page content so all pages must contain the token value somewhere, which could make it time consuming to implement for a large site.

Custom HTTP Header (downstream)

  1. User successfully logs in.
  2. Server issues auth cookie.
  3. User clicks to navigate to a form.
  4. Page loads in browser, then an AJAX request is made to retrieve the CSRF token.
  5. Server generates CSRF token (if not already generated for session), stores it against the user session and outputs it to a header.
  6. User submits form (token is sent via hidden field).
  7. Server checks hidden field matches session stored token.

Advantages:

Disadvantages:

  • Doesn't work without an AJAX request to get the header value.
  • All forms must have the value added to its HTML dynamically.
  • Any AJAX POSTs must also include the value.
  • The page must make an AJAX request first to get the CSRF token, so it will mean an extra round trip each time.
  • Might as well have simply output the token to the page which would save the extra request.

Custom HTTP Header (upstream)

  1. User successfully logs in.
  2. Server issues auth cookie.
  3. User clicks to navigate to a form.
  4. If not yet generated for this session, server generates CSRF token, stores it against the user session and outputs it in the page content somewhere.
  5. User submits form via AJAX (token is sent via header).
  6. Server checks custom header matches session stored token.

Advantages:

Disadvantages:

  • Doesn't work with forms.
  • All AJAX POSTs must include the header.

Custom HTTP Header (upstream & downstream)

  1. User successfully logs in.
  2. Server issues auth cookie.
  3. User clicks to navigate to a form.
  4. Page loads in browser, then an AJAX request is made to retrieve the CSRF token.
  5. Server generates CSRF token (if not already generated for session), stores it against the user session and outputs it to a header.
  6. User submits form via AJAX (token is sent via header) .
  7. Server checks custom header matches session stored token.

Advantages:

Disadvantages:

  • Doesn't work with forms.
  • All AJAX POSTs must also include the value.
  • The page must make an AJAX request first to get the CRSF token, so it will mean an extra round trip each time.

Set-Cookie

  1. User successfully logs in.
  2. Server issues auth cookie.
  3. User clicks to navigate to a form.
  4. Server generates CSRF token, stores it against the user session and outputs it to a cookie.
  5. User submits form via AJAX or via HTML form.
  6. Server checks custom header (or hidden form field) matches session stored token.
  7. Cookie is available in browser for use in additional AJAX and form requests without additional requests to server to retrieve the CSRF token.

Advantages:

  • Simple to implement.
  • Works with AJAX.
  • Works with forms.
  • Doesn't necessarily require an AJAX request to get the cookie value. Any HTTP request can retrieve it and it can be appended to all forms/AJAX requests via JavaScript.
  • Once the CSRF token has been retrieved, as it is stored in a cookie the value can be reused without additional requests.

Disadvantages:

  • All forms must have the value added to its HTML dynamically.
  • Any AJAX POSTs must also include the value.
  • The cookie will be submitted for every request (i.e. all GETs for images, CSS, JS, etc, that are not involved in the CSRF process) increasing request size.
  • Cookie cannot be HTTP Only.

So the cookie approach is fairly dynamic offering an easy way to retrieve the cookie value (any HTTP request) and to use it (JS can add the value to any form automatically and it can be employed in AJAX requests either as a header or as a form value). Once the CSRF token has been received for the session, there is no need to regenerate it as an attacker employing a CSRF exploit has no method of retrieving this token. If a malicious user tries to read the user's CSRF token in any of the above methods then this will be prevented by the Same Origin Policy. If a malicious user tries to retrieve the CSRF token server side (e.g. via curl) then this token will not be associated to the same user account as the victim's auth session cookie will be missing from the request (it would be the attacker's - therefore it won't be associated server side with the victim's session).

As well as the Synchronizer Token Pattern there is also the Double Submit Cookie CSRF prevention method, which of course uses cookies to store a type of CSRF token. This is easier to implement as it does not require any server side state for the CSRF token. The CSRF token in fact could be the standard authentication cookie when using this method, and this value is submitted via cookies as usual with the request, but the value is also repeated in either a hidden field or header, of which an attacker cannot replicate as they cannot read the value in the first place. It would be recommended to choose another cookie however, other than the authentication cookie so that the authentication cookie can be secured by being marked HttpOnly. So this is another common reason why you'd find CSRF prevention using a cookie based method.

Lutz Prechelt
  • 29,204
  • 6
  • 52
  • 79
SilverlightFox
  • 28,804
  • 10
  • 63
  • 132
  • Thanks for the detailed answer. The point about the cookie remaining valid past the lifetime of the initial page load, for apps that are not single-page apps, is a good one. – metamatt Dec 11 '13 at 22:05
  • 8
    I'm not sure I understand how "AJAX request is made to retrieve the CSRF token" (step 4 in both "custom header: downstream" sections) can be done securely; since this is a separate request, the server doesn't know who it's coming from; how does it know it's safe to divulge the CSRF token? It seems to me if you can't get the token out of the initial page load, you lose (which makes the custom downstream response header a nonstarter, unfortunately). – metamatt Dec 11 '13 at 22:09
  • 1
    It would be done in such a way that the Same Origin Policy protects the response from being read by a different domain (e.g. AJAX POST). – SilverlightFox Dec 12 '13 at 08:33
  • 4
    What I mean is, what stops someone else (a would-be CSRF forger) from making the request back to the server (including the session cookie)? This forger doesn't have to steal it from the client (which is what Same Origin Policy would prevent); the forger can just ask the server nicely and the server would supply it. So I still don't see how the server can safely supply the CSRF token *in a separate request from the main page load*. – metamatt Dec 12 '13 at 17:05
  • 7
    Because the forger won't have the session cookie. They might have their own session cookie, but as the CSRF token is associated to a session, their CSRF token won't match the victim's. – SilverlightFox Dec 12 '13 at 17:09
  • 36
    In my understanding of the CSRF attack, the forger does have *my* session cookie. Well, they don't actually get to *see* the cookie, but they have the ability to provide it in their forged requests, because the requests are coming from my browser and my browser supplies my session cookie. So from the server's point of view, the session cookie alone can't distinguish a legitimate request from a forged request. This is in fact the attack we're trying to prevent. BTW thanks for your patience in talking this through, especially if I'm confused about this. – metamatt Dec 12 '13 at 17:36
  • 9
    They have the ability to supply the auth cookie, but they cannot read the response that contains the CSRF token. – SilverlightFox Dec 12 '13 at 17:40
  • Please update the answer to mention that any approach that uses cookies is vulnerable to sub-domain attacks: http://security.stackexchange.com/q/33851/5002 -- If you control all sub-domains you should be fine. – Gili Jun 05 '14 at 19:47
  • For the `Set-Cookie` method, I recommend merging steps 2 and 4 (so the CSRF token is generated once); otherwise people (like myself) might make the mistake of trying to generate a new token per form. See http://stackoverflow.com/q/24225682/14731. – Gili Jun 16 '14 at 04:24
  • 4
    You have 'Any AJAX POSTs must also include the value.' listed as a disadvantage in all cases. I would say this is not a disadvantage of any one method, but a requirement of CSRF protection in general. – Ali Gangji Aug 25 '14 at 03:40
  • 1
    @AliGangji: Not necessarily, but certainly with the methods the OP mentions. CSRF protection that does not require a token value include checking [`X-Requested-With`](http://stackoverflow.com/a/22533680/413180) or checking the [`Referer or Origin`](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#CSRF_Prevention_without_a_Synchronizer_Token) headers. – SilverlightFox Aug 25 '14 at 07:54
  • 1
    @SilverlightFox: Yup, you are correct. I should say it's requirement of any token based method. – Ali Gangji Aug 25 '14 at 15:28
  • I went with the Cookie approach as the headers were being wiped out by a middle system that our application was going through. Excellent Post. Thank you – Rami Del Toro Jun 28 '15 at 15:16
  • 11
    @metamatt Sorry for the necro, but I'll do it for people that wander in. In my understanding the attacker doesn't typically have access to the response. CSRF is primarily used to _cause side effects_, rather than directly gathering data. For example, a CSRF attack script might force a privileged user to escalate the privileges of the attacker, disable a security setting, or force a logged-in paypal user to send a transfer to a specific email address. In none of these cases does the attacker care about the response, which is still sent to the victim's browser; only the outcome of the attack. – terrabruder May 12 '16 at 02:31
  • "If a malicious user tries to read the user's CSRF token in any of the above methods then this will be prevented by the Same Origin Policy" What happens if you have an XSS lurking on your application's views ? – mak Jul 28 '16 at 17:38
  • 1
    @mak If an attacker has XSS ability then they don't need CSRF. XSS almost always has a greater impact. – SilverlightFox Jul 28 '16 at 17:42
  • 1
    There's one fairly big disadvantage to using Set-Cookie downstream approach -- it makes it way harder to implement XSRF protection in clients other than browser (mobile applications that use the same HTTP endpoints, desktop applications e.g. Electron). Cookies are often handled separately, and are not as easily available as a custom downstream header. – Dzmitry Lazerka Feb 21 '17 at 18:35
  • Hello! I've also been wondering about approaches to mitigate this vulnerability and have thought of one particular way to protect RESTful APIs and SPAs. I've asked my question here for opinions from the community. Please have a look! https://stackoverflow.com/questions/49597702/protection-against-csrf-and-xss-hashing-encrypting – Anindit Karmakar Apr 01 '18 at 12:08
  • Hi, how come CSRF Token can't be in an HTTP Only cookie? – t3__rry Feb 22 '19 at 16:26
  • 2
    @t3_ Because JavaScript needs access to the value. – SilverlightFox Feb 22 '19 at 20:39
  • Can someone explain how the token can be stored in an httpOnly cookie if the upstream custom header approach is used? How would I add the custom header to any ajax request if I can't access the csrf token value from the cookie? – Florian Richter Jul 05 '19 at 12:22
  • Did session storage exist when this answer was written? It would seem to me that a value stored there would have the advantages of a cookie in terms of availability, but would also not be present in Cookie headers, reducing request size and obviating the danger that poorly thought-out server-side code would try to validate something it shouldn't. – TRiG Aug 02 '19 at 10:25
  • @SilverlightFox using Setcookie method where cookie cannot be http only , don't you think it is now vulnerable to xss cross site scripting attacks as xss can bypass same origin policy ? So implenting CSRF using Setcookie approach makes it even more vulnerable bto xss attacks? – TechAJ Oct 28 '19 at 10:14
  • 2
    @TRIG Here's a great article on that very subject: https://portswigger.net/research/web-storage-the-lesser-evil-for-session-tokens – SilverlightFox Oct 31 '19 at 19:38
  • 2
    @ApoorvaJain If XSS is achieved, all bets are off in terms of defending against CSRF. An attacker can use script to get the values they need to include in any request, and of course the cookies will be automatically sent. – SilverlightFox Oct 31 '19 at 19:39
  • Thanks, @SilverlightFox. That really was interesting. – TRiG Oct 31 '19 at 20:31
  • Hm why would CSRF be an issue if the token has SameSite on it? – Dominic May 17 '21 at 06:37
  • @Dominic It could be an issue if a subdomain is vulnerable to XSS: https://jub0bs.com/posts/2021-01-29-great-samesite-confusion/ Basically SameSite doesn't mean same domain. `foo.example.com` and `xss-vulnerable.example.com` are the same "site". – SilverlightFox May 18 '21 at 08:30
77

Using a cookie to provide the CSRF token to the client does not allow a successful attack because the attacker cannot read the value of the cookie and therefore cannot put it where the server-side CSRF validation requires it to be.

The attacker will be able to cause a request to the server with both the auth token cookie and the CSRF cookie in the request headers. But the server is not looking for the CSRF token as a cookie in the request headers, it's looking in the payload of the request. And even if the attacker knows where to put the CSRF token in the payload, they would have to read its value to put it there. But the browser's cross-origin policy prevents reading any cookie value from the target website.

The same logic does not apply to the auth token cookie, because the server is expects it in the request headers and the attacker does not have to do anything special to put it there.

Tongfa
  • 1,675
  • 14
  • 9
  • Surely though, an attacker does not need to read the cookie in the first place. They can just insert an image on the hacked site with `src='bank.com/transfer?to=hacker&amount=1000` which the browser will request, complete with the associated cookies for that site (`bank.com`)? – developius Mar 23 '17 at 15:23
  • 3
    CSRF is for validating the user on the client side, and not for protecting the site generally from a server side compromise as you suggest. – Tongfa Mar 24 '17 at 20:50
  • 4
    @developius sending the cookie is not enough to satisfy CSRF protection. The cookie contains the csrf token, as sent by the server. The legitimate client must read the csrf token out of the cookie, and then pass it in the request somewhere, such as a header or in the payload. The CSRF protection checks that the value in the cookie matches the value in the request, otherwise the request is rejected. Therefore, the attacker does need to read the cookie. – Will M. Apr 12 '18 at 20:26
  • 1
    This answer was very to the point to the original poster's question and was very clear. +1 Thank you. – java-addict301 Feb 04 '19 at 20:24
  • @Tongfa - thanks, this helped me understand better. Am I right to assume that the CSRF Token should NOT be placed in the header? it must be somewhere in the body? – zerohedge Oct 12 '19 at 22:26
  • Note that this approach means any malicious javascript on the page can read the provided CSRF cookie at any time to find this token and submit that (with the browser providing the main cookie token) to succesfully make a request on behalf of a user. If building a SPA, perhaps an alternative would be the server providing the token one time (instead of as a cookie) and the client storing it inside an instance that rouge Javascript embeds can't access (if this is even possible). However, the malicious Javascript could simply use Angular/React/Vue object storing the value to make the request. – Xeoncross Nov 27 '19 at 21:11
  • @Xeoncross - That's not CSRF, that's XSS which is a different topic with different solutions. – Tongfa Nov 28 '19 at 22:22
  • @zerohedge - That's a good question. Most (all?) implementations I've seen do include it in the body. Cookies are automatically by the browser sent in the headers, using a CSRF token value sent via that mechanism would be an error. I don't immediately see anything wrong with using a custom header that contains the CSRF token. The key is that it's custom behavior implemented by your own software and not some automatic function of the browser implementation. – Tongfa Nov 28 '19 at 22:36
11

My best guess as to the answer: Consider these 3 options for how to get the CSRF token down from the server to the browser.

  1. In the request body (not an HTTP header).
  2. In a custom HTTP header, not Set-Cookie.
  3. As a cookie, in a Set-Cookie header.

I think the 1st one, request body (while demonstrated by the Express tutorial I linked in the question), is not as portable to a wide variety of situations; not everyone is generating every HTTP response dynamically; where you end up needing to put the token in the generated response might vary widely (in a hidden form input; in a fragment of JS code or a variable accessible by other JS code; maybe even in a URL though that seems generally a bad place to put CSRF tokens). So while workable with some customization, #1 is a hard place to do a one-size-fits-all approach.

The second one, custom header, is attractive but doesn't actually work, because while JS can get the headers for an XHR it invoked, it can't get the headers for the page it loaded from.

That leaves the third one, a cookie carried by a Set-Cookie header, as an approach that is easy to use in all situations (anyone's server will be able to set per-request cookie headers, and it doesn't matter what kind of data is in the request body). So despite its downsides, it was the easiest method for frameworks to implement widely.

Community
  • 1
  • 1
metamatt
  • 12,399
  • 7
  • 42
  • 55
  • 7
    I might be stating the obvious, this does mean that cookie can not be httponly correct? – Photon Jul 20 '14 at 23:42
  • 2
    only for ajax requests (where JS needs to know the csrf cookie's value in order to resend it on next request in the second channel (either as form data or header)). There's no reason to require the csrf token be HttpOnly if the session cookie is already HttpOnly (to protect against XSS) since csrf token isn't valuable by itself without associated session. – cowbert Oct 13 '18 at 19:56
2

Besides the session cookie (which is kind of standard), I don't want to use extra cookies.

I found a solution which works for me when building a Single Page Web Application (SPA), with many AJAX requests. Note: I am using server side Java and client side JQuery, but no magic things so I think this principle can be implemented in all popular programming languages.

My solution without extra cookies is simple:

Client Side

Store the CSRF token which is returned by the server after a succesful login in a global variable (if you want to use web storage instead of a global thats fine of course). Instruct JQuery to supply a X-CSRF-TOKEN header in each AJAX call.

The main "index" page contains this JavaScript snippet:

// Intialize global variable CSRF_TOKEN to empty sting. 
// This variable is set after a succesful login
window.CSRF_TOKEN = '';

// the supplied callback to .ajaxSend() is called before an Ajax request is sent
$( document ).ajaxSend( function( event, jqXHR ) {
    jqXHR.setRequestHeader('X-CSRF-TOKEN', window.CSRF_TOKEN);
}); 

Server Side

On successul login, create a random (and long enough) CSRF token, store this in the server side session and return it to the client. Filter certain (sensitive) incoming requests by comparing the X-CSRF-TOKEN header value to the value stored in the session: these should match.

Sensitive AJAX calls (POST form-data and GET JSON-data), and the server side filter catching them, are under a /dataservice/* path. Login requests must not hit the filter, so these are on another path. Requests for HTML, CSS, JS and image resources are also not on the /dataservice/* path, thus not filtered. These contain nothing secret and can do no harm, so this is fine.

@WebFilter(urlPatterns = {"/dataservice/*"})
...
String sessionCSRFToken = req.getSession().getAttribute("CSRFToken") != null ? (String) req.getSession().getAttribute("CSRFToken") : null;
if (sessionCSRFToken == null || req.getHeader("X-CSRF-TOKEN") == null || !req.getHeader("X-CSRF-TOKEN").equals(sessionCSRFToken)) {
    resp.sendError(401);
} else
    chain.doFilter(request, response);
}   
Lokesh
  • 2,014
  • 5
  • 23
  • 39
  • I think you would want CSRF on a login request. You seem to be using the CSRF token also as a login session token. It also works to have them as separate tokens, and then you can use CSRF on any endpoint, whether the user is logged in or not. – Tongfa Nov 28 '19 at 22:43