Here’s a question from a co-worker I get quite often:
Considering that curl requests do not have the restriction of the fetch API, how does this browser policy [CORS] prevent circumventing abuse or misuse of server endpoints if an attacker can anyways do so by default via curl? And since a bad actor can use curl what’s the real world effectiveness of the browser policy in the first place?
While ‘Cross Origin Resource Sharing’ is a very important security tool for the modern web, it solves a problem that’s very specific to the security architecture of the web which leads to many misconceptions.
This question comes down to the crux of why CORS exists. It is often misconstrued that CORS is a way of preventing different websites from receiving information from each other, but it’s quite the opposite. Let’s talk about a world without CORS first.
If I create a website,
As a result, before CORS, if I wanted to write a website that retrieved data from another, such as a Google API I had to do it via my own backend server. Take a look at this flow:
Here, my webpage
http://mywebsite.com/index.html requests two things: (1) my account info from
mywebsite.com/me.json and (2) the latitude and longitude of London via
api.google.com/lookup. However, Same Origin Policy prevents us from requesting Google directly, so we configure our server to make the request for us.
CORS introduced a suite of headers that start with
mywebsite.com to access the response of this request.
So why don’t attackers ignore the
Access-Control-Allow-Origin header? Because it doesn’t get them anything. If I’m forming an HTTP request with CuRL, all the information I’m sending to the server is already known to me, and everything I’m accessing I already had access to. What the attacker really wants is for the victim’s browser to relinquish some of the information it has about the victim.
Partially for legacy reasons, requests in browsers have certain ambient privileges (“ambient authority”). My website in the browser will be sending the request from the user’s IP address, potentially with the user’s own login session. CORS navigates allowing access to certain resources that have stated that they’re aware of what this might entail via
Access-Control-Allow-Origin and preventing old applications using legacy behavior becoming insecure.
Why don’t we just let anything request anything else and block cookies?
This may sound to an extent like anarchy, but can’t we just block cookies for CORS requests and let anyone load anyone else? At that point, I’m not going to be able to get information about someone’s logged in account, right?
This is not at all a bad idea. In fact, it’s actually part of CORS.
Access-Control-Allow-Origin: * does exactly this: it allows any website to load your resource, but prevents cookies from being sent. It still has to be opted into, however. The world of
Access-Control-Allow-Origin: * is arguably significantly more secure, because it eliminates the entire category of CSRF vulnerabilities nearly entirely.
But it’s still opt-in because there are, unfortunately plenty of systems that use something other than cookies for authentication. For example, many printers and home routers do not bother with cookie based sessions, because it is assumed that if you can make a connection to the service you are already in the network. The same goes for many web servers that software starts on your computer.
This is an incorrect assumption because of CSRF — it’s still possible to tell the browser to load the resource via an HTML tag like
<img src=your.router/logout/> and it’ll still execute and log the current user out but there’s no way to retrieve a response. If one could retrieve a response, it’d be significantly worse.
Origin probably being the most dangerous but there’s also
Host and all sorts.
I hope this article helps shed some light on why CORS exists and how it fits into browser security. The security benefits of CORS are not inherent to the CORS protocol itself — after all, as noted you can avoid it completely — but the alternatives it avoids.
A final point I’d like to make is that CORS is often thought of as a complex protocol requiring a backend, and this makes it a huge pain for a lot of people serving certian kinds of assets. Many implementations of CORS use the requesting
Origin header to determine if the request should be allowed, and return that same origin if it is.
For the most part, CORS is completely static, and one can just send
Access-Control-Allow-Origin: myorigin or
Access-Control-Allow-Origin: * in response to any request. In earlier drafts of
Access-Control-Allow-Origin, this was more apparent as the header could include multiple origins at once, and
Access-Control-Max-Age facilitates caching of a static CORS policy for a time.
I don’t quite know why
Access-Control-Allow-Origin now doesn’t allow a list of whitelisted origins. For a time, Firefox allowed this pattern. My guess is that early implementations of CORS allowed only one origin and that pattern stuck. Content Security Policy headers use this pattern for its killer feature
allow-src and its successor to
X-Frame-Options , the
frame-ancestors directive amongst others.