Exploiting CORS Misconfigurations
If you can find an unrestricted CORS endpoint, that also responds to the HTTP override headers, then potentially you can use it to access endpoints that aren’t enabled for CORS, bypass CSRF protections, and also deliver an XST (which will give you access to cookies protected by the httpOnly attribute).
The idea for this research came about after repeatedly hearing people say that CORS issues are irrelevant (which to be fair, the generic “allows any origin” alert you get from many scanners mostly is).
But I really don’t like being told something isn’t possible, so instead I set out to have a proper rummage and see what I could actually do with CORS. Apart from being fun, along the way I found a quirk that affected all the main browsers, and also some web platform zero-days too. What’s not to like about that?
Anyway, back in the Dark Ages, before CORS existed, we just had the same-origin policy, which limited the types of requests that could be sent between sites. The introduction of CORS brought a huge amount of flexibility to cross-origin requests, and made the web the rich experience that it is today. But it is also a bit fiddly to setup, and because of that, is easy to get wrong. Which is a bad thing, if you’re looking to secure your site.
It’s a bit like the classic firewalling problem. The engineer spends the morning trying to make a fiddly rule work well, then just gives up, clicks the any button, and makes a mental note to come back and fix it properly sometime (i.e. when hell freezes over).
CORS is the same. Out in the real world, there are a lot of sites configured with a * for methods, origins and headers. Either because that is the default the platform ships with, or because someone gave up and clicked the any button.
In the Red Corner
From an attack point of view, the reason that you should be interested in CORS is that you can get a request to fire within a user’s session, and in doing so leverage their credentials and network access.
However, before we go any further, if you’re not already familiar with the way CORS works (and CORS Preflight in particular), then I’d recommend reading the Mozilla CORS guide which is a good primer.
Once upon a time, an engineer (let’s call her Karren) was struggling to make an application work through a firewall, when she had an epiphany. She would fix the problem by implementing a shadow set of method, host and URI headers that the firewall didn’t know about. And in that moment, she created dozens of possible combinations for accessing an app through a restrictive firewall. And even better, she then told the rest of her engineer buddies, who spawned similar abominations. Love you Karren!
Anyway, wouldn’t it be cool if you could take an unrestricted CORS configuration, and use these override headers to basically ignore CORS and access whatever part of the app stack you wanted to? Well, as it turns out, that’s exactly what you can do. Yay!
The reason that this works is that any useful CORS request will generate two separate HTTP requests. The first is the Preflight (which has minimal headers, and no overrides) and then the second is the actual CORS request (which has your override headers). That means that each HTTP request will get a response from a different part of the app stack. The Preflight will be answered by the unrestricted CORS endpoint, but the actual request goes elsewhere. OooOOoooh!
To make this work in practice, you will need to do your recon thoroughly, then glue all the separate bits together into a workable attack.
The first thing to do is to find the unrestricted CORS endpoints: the ones which allow authentication plus the override headers. In practice, this means making a lot of CORS Preflight requests, to enumerate the possible origin, method and header combinations (because when authentication is enabled, a * in the response is treated as a literal, not a wildcard).
It’s also worth noting that some CORS endpoints will take an access-control-request-headers populated with all the desired headers, then respond with an access-control-allow-headers that contains the subset of allowed headers. You can use this to cut the number of requests you need to send. However, some other CORS endpoints will reject the request outright if they do not support one of the requested headers. These you will need to enumerate empirically.
The second thing to do is to check whether these unrestricted CORS endpoints also respond to one of the override headers too. If this isn’t something you are already familiar with, then it is time to deploy your google-fu. What am I, your mother?
Putting It All Together
Ok, so you’ve found an unrestricted CORS endpoint that also accepts the override headers, now what? Well, the actual detail depends on the app stack in use, and what you want to achieve.
Let’s say that your target responds to one of the host override headers (forwarded, x-forwarded-host, x-forwarded-server, x-host and x-http-host-override). Because the typical app stack has a common set of load balancers and reverse-proxies, you can often use these headers to redirect your request between servers.
Then there are the URI override headers (x-original-url, x-rewrite-url and x-url), which allow you to redirect your request to other endpoints on the same host.
And finally, the method override headers (x-http-method, x-http-method-override, x-method and x-method-override), which allow you to swap methods.
It’s worth noting that any combination of overrides that redirect the request to a non-CORS endpoint may not return the correct CORS headers, and so will “fail” from the perspective of the browser (and block access to the response). However, that will not stop the request from working.
This is where it gets fun. A lot of CSRF protection mechanisms rely on the token only being available from an endpoint that doesn’t allow CORS requests. However, that isn’t strictly true, is it ;)
XST hasn’t been a thing for years now, because the browsers have blocked direct access to the TRACE method. However, again, that isn’t strictly true.
Also, because this is CORS, all it takes is the right combination of misconfigurations on a site, and you can use XST to completely compromise the user accounts with one fetch call (bypassing any cookie httpOnly restrictions). Full account compromise, and all from a CORS misconfiguration. Boom!
I had a lot of fun with this in the last few months, whilst the main browsers were busy implementing fixes. However, as of now, this shouldn’t be possible with the latest browser code (though there will of course be vulnerable versions hanging around for years to come).
In the Blue Corner
There are three things you can immediately do to defend against these kinds of request header issues in general, and the CORS issues specifically.
Firstly, add signatures to your IDS/SIEM that trigger on the main override headers (this should be as early in your app stack as possible, so it catches headers entering your environment, not those added by any of your internal components). Monitor the output closely, as you may find that you have apps deployed that depend on some of these headers:
Secondly, strip (or reject) any of these headers that your apps don’t need as early as possible in your app stack.
And finally, find all your endpoints that respond to CORS Preflight requests, and ensure that they accept the minimum set of headers required to make the app work. Pay special attention to the access-control-allow-headers response (which should not be a * and also should not simply reflect back the request either).
XST PoC Site
If you want to see it work for real, then there is an XST PoC site available that you can experiment with here: https://xst.scarlet.ae
I just wanted to say a big “thanks” to the teams from whatwg, Firefox, Google and Apple, who were efficient, professional and a complete pleasure to work with. Love you long-time!