CORS Lite

Dakota Nelson//

Cross Origin Request Sharing (CORS) is complicated, and that complexity creates a lot of places where security vulnerabilities can sneak in. This article will give you a “lite” overview of CORS, with a focus on security vulnerabilities that we see most frequently on tests. This isn’t intended to be a full CORS primer – if you’re interested in really diving in, there’s a resources section at the end that will help you get started.

CORS has many facets, so let’s cover a few of the more common cases one by one and see what can go wrong.

The most basic part of the CORS specification is the “Access-Control-Allow-Origin” header, the basis around which everything else is built. This header informs the browser what origins are allowed to access a particular resource. Put another way, if the JavaScript in your webapp (which the user’s browser loaded at app.example.com) makes a request to api.example.com, that request is cross-origin, and by default will be denied (i.e. the browser won’t issue the request, or will ignore the result from the request) unless api.example.com returns an “Access-Control-Allow-Origin” header telling the browser that it’s ok.

In all the following images, the big green thumbs-up means the scenario was a success and happened, and the big red thumbs-down means the browser refused to carry out the scenario (though it might not refuse until partway through – CORS is complicated and I’m skimming over some stuff here).

For something like an API, the Access-Control-Allow-Origin header is frequently set to “*”, which allows any domain to make cross-origin requests to the API. This means any javascript, anywhere, can access your API. Thankfully, though, even with an Access-Control-Allow-Origin header, cross-origin requests won’t transmit cookies or other authentication material, so this header by itself is actually fairly safe.

The Access-Control-Allow-Origin header doesn’t usually open up security problems on its own, but it can make other problems much worse when it’s added.

The big risk comes with the addition of another CORS header: Access-Control-Allow-Credentials. When this header is returned, cross-origin requests are allowed to include cookies and other authentication material. This is where problems really begin. If a user visits example.com, and example.com sets a cookie in their browser (say, after they log in), the Access-Control-Allow-Credentials header will allow that cookie to be sent by requests issued by that same browser from other domains. That means those domains can use cookies set by example.com to carry out actions on example.com, no matter who controls the javascript running on those domains!

If a request to example.com returns an Access-Control-Allow-Origin: foo.bar header and an Access-Control-Allow-Credentials: true header, then any scripts on foo.bar execute with privileges as if they were native scripts on example.com. This means that XSS vulnerabilities on foo.bar are equivalent to XSS vulnerabilities on example.com! If you’re the owner of example.com, that’s a lot of trust to place in the owner of another site, and you should think about it very carefully before you do.

There are a couple of protections in place to prevent accidents around the Access-Control-Allow-Credentials header because it’s so dangerous. For one, it cannot be combined with Access-Control-Allow-Origin: *. Some sites get around this by just reflecting back whatever is sent in the Origin header in the Access-Control-Allow-Origin header in their response, bypassing this restriction. If you ever contemplate doing such a thing, please think very carefully about the risk you’re taking on before you do. Another restriction on the Access-Control-Allow-Credentials header is on the client side – XMLHttpRequests must deliberately set a “withCredentials” flag in order to request that credentials be sent, to make sure you absolutely want the browser to send cookies and other sensitive material with your cross-domain request.

These protections are kind of confusing, so I’ve set up a grid below that shows the different scenarios you may find yourself in.

Along the left, you can see the headers returned by the server, while along the top you can see whether the client javascript set the “withCredentials” flag. Any red box means the request was entirely denied by the browser – for instance, the first two rows are entirely red because cross-domain requests aren’t allowed without the Access-Control-Allow-Origin header. In the boxes themselves, you can see whether cookies were sent by the client. If you’d like to play with this more and see the exact code that created this grid, you can run this example yourself – go grab it from Github at https://github.com/DakotaNelson/cors-test-server.

Here’s the main takeaway, though: if you’re reflecting the Origin header into your Access-Control-Allow-Origin header, and setting Access-Control-Allow-Credentials: true at the same time, any attacker able to execute malicious javascript in one of your users’ browsers, no matter where that javascript is, can hijack that user’s cookies set by your application and act on that user’s behalf. If there’s an XSS vulnerability in a site they commonly visit, the attacker is able to execute javascript in an advertisement targeted to them, or the attacker can execute javascript after phishing the user to an attacker-controlled page, it’s game over if that user has a session on your application.

Some applications attempt to split the difference, and validate the origin header manually instead of just reflecting it – so that e.g. anything matching “*.example.com” is considered “safe” and will be returned in the Access-Control-Allow-Origin header.

Do you see the problem here? An attacker who can convince a user to visit “example.com.malicious.com” (likely through phishing) can conduct the same attacks as before, because that site matches the pattern! This is still better than reflecting back any arbitrary origin in the Access-Control-Allow-Origin header, but as always, validation is very tricky to get right.

There are other twists and turns to CORS, and headers we haven’t covered, but the key that you need to understand is this: CORS is complicated, and any time you’re considering doing anything with it, it’s worth doing some research into the implications. The resources below are things I’ve found useful in understanding the security implications of CORS, and if you’re the hands-on type you should definitely set up the CORS test server and poke at it for a while to help you understand what’s going on.

Good luck, and I hope you enjoyed this CORS lite!

 

Other resources:

https://en.wikipedia.org/wiki/Cross-origin_resource_sharing

https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

https://portswigger.net/blog/exploiting-cors-misconfigurations-for-bitcoins-and-bounties

https://web-in-security.blogspot.com/2017/07/cors-misconfigurations-on-large-scale.html