CORS and CSRF – Everything there is to know! by Gautam Kumar Samal on April 28, 2020 434 views
What is CORS?
Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional HTTP headers to tell browsers to give a web application running at one origin, access to selected resources from a different origin. A web application executes a cross-origin HTTP request when it requests a resource that has a different origin (domain, protocol, or port) from its own.
As we can clearly see, it’s a browser thing. Most browsers enforce a certain standard of HTTP headers to comply with CORS requests. So, why do we need this? Is it adding any extra value? To answer these, let’s consider a simple use case.
Imagine we have a website, up and running on an imaginary domain- http://domain-a.com. Users can sign up and login to access services. Some services don’t need a login process like fetching the company details or contact information. As REST API is stateless, the server depends on specific tokens, mostly cookies to identify the requester. There is no other full-proof way to get around it. Now our website http://domain-a.com is going to trust the browser to send the cookies along when an authenticated user makes a request.
But what if another web page http://domain-b.com sends a request to an API of domain-a? It’s the same as clicking a button on domain-a except it’s originating from another domain/website. There are valid use cases to this. Take an example of twitter. Many other websites also include twitter APIs in their website to show information at one place, improving the user experience. This is where a conflict arises. So, to differentiate what is intended and what is not, such standards have been set. As per the rule, If the server needs to send a particular set of headers based on origin of the request. This is considered as acknowledgement that the service allows the said requester other than the same domain.
If the headers are not provided, the browser blocks the response or denies it completely based on preflight request. It’s also important to note that CORS policy only applies in browsers and that too XHR requests, generated from scripts. If you directly hit an API in the URL bar or submit a HTML form, it’s not under CORS policy.
What is CSRF?
Cross-site request forgery, also known as one-click attack or session riding and abbreviated as CSRF (sometimes pronounced sea-surf) or XSRF, is a type of malicious exploit of a website where unauthorized commands are transmitted from a user that the web application trusts.
Cross-site forgery is quite a common method to exploit the browser. This is why CORS got introduced, to restrict malicious sites performing an action to a targeted site under the hood.
Why is this serious? Let’s say you have logged in to Gmail on your browser. On another tab, you got a malicious site. The site can bluff and try to hit an API (f.e. http://gmail.com/send/mail). This will send the API to Gmail server with the credentials that’s already there in your browser and without you knowing, a mail will be sent or attempted to be.
You would think CORS policy would fix this, right? In a obviously way, Gmail can deny access headers to other sites. Well, yeah! But CORS isn’t full-proof, see the last paragraph of CORS section. While The same origin policy applies to the XHR request, form submissions are still vulnerable. To be honest, XHR is not completely secure under this policy either. If your server doesn’t force users to send complex requests (like including a custom header or set Content-Type = application/json), it will be considered as a simple request and the server will get hit even if the browser denies it.
Okay, that makes sense! Now let’s secure our website.
Let’s start with a simple server.
The base URL will render an index page and will set a cookie – “Visited”. Let’s assume that’s something we’ll use to identify if a user has visited the page before, just like being logged in.
We hit the address – http://localhost:3000 and can see the cookie being set.
CSRF Attack Methods:
#1 – Ajax/XHR Requests
We are going to create another HTML file with the following script doing an automatic request to the targeted domain.
We have used Fetch Api, which is similar to XMLHttpRequest. Let’s see what happens when we hit the script in the same browser.
In network tab:
If we see the client-side, the browser blocked the request since we don’t present any CORS header but it did initiate the request and sent the cookie (because of “credentials: include”).
But if we see in the server, it’s a hit 😒
(Console is being printed because the API executed successfully)
So, it failed to secure access from cross domain.
#2 – Form Submission
This is another method to do cross origin requests in a more native way and we know CORS doesn’t apply on the form submissions. Then it should be easy.
Let’s have a HTML file with the following form element.
And if we click naively on the submit button, we’ll see it requests another domain and succeeds without any blockade.
Once you click – it also renders the response.
Approach #1 – Let’s go for optimal solution
We just saw our simple app when not supporting any CORS activities falls victim to all the methods of exploitation. Let’s work on additional measures to fix this. Remember this, following solutions work for demonstration of concept only. In production, prefer good libraries that back this concept.
First step: Let’s add another cookie to track this. We can conveniently name this as XSRF-TOKEN. In our server code, add this snippet before routes.
As you see, we are going to add another cookie called XSRF-TOKEN. This token needs to be random and your server can only identify the same. You can use cryptographic solutions as well. Well, in any case the operational behaviour will remain the same.
So, we are setting the cookie and verifying it with a POST request. You can change this condition based on your application behaviour. One may wonder if the token is a cookie the same as “Visited”, then it will be sent from the browser too, not solving the problem. Yes! You are right. The only difference here is the “SameSite” parameter. When a cookie is set as SameSite, Cross origin websites can’t read this cookie, so the API can’t send it thus making the verification failed.
Well! Looks like we got it covered. If the XSRF cookie can’t be passed on, then CSRF attacks won’t work. I wish it would be that easy. So, there is a catch. The “sameSite” attribute is supported in newer browsers only. Bowsers older before 2017 or possibly IE, don’t support this fully. That would mean Cookies are just Cookies irrespective of the SameSite flag. 😮
Our solution should be robust enough to avoid attacks in older browsers too. So, let’s keep the SameSite flag as it’s a good security improvement and we’ll implement something else alongside.
Approach #2 – Take a step back 😉
We are not going to rely on Cookies cause they clearly aren’t reliable. This time we’ll force our clients to send XSRF as header as well as cookies. This is called double submitting. So, we change our methods a bit.
Along with cookies, we’ll expect the one of a header to be set with the same value. Our own client can easily do this. Let’s see how the other attackers are doing..?
To explain a bit, setting a custom header like “x-xsrf-token” is a bit challenging. We can never do it in the form, so the attack #2 is being avoided completely.
In XHR, adding a custom header is possible. I mean that’s the only way our own client will do genuine requests. But CORS policy will mark the request as complex if a custom header is being sent, thus requiring a preflight verification.
Will result as
See the highlighted part. It’s a preflight request. So the actual request never made it. Big Sigh! 😊
We can clearly see that the XHR and Form submission will be blocked this way.
We have used simple string tokens to demonstrate how this works. For real development purposes, use trusted modules like csurf or something similar. They generate cryptographically strong pseudorandom keys that can be easily verified on the server.
In addition to that keep in mind to change the tokens randomly and if possible, on each request. But the old keys should be verifiable as well, hence the suggestion to go for a supported module. Constantly changing keys are required to thwart a possible breach attack.
At the end, this is everything we have learned till far on the subject of CSRF and CORS. Things can/will change in the future. So, keep up and stay safe!😊