TLDR: There's nothing stopping malicious code from spoofing the origin. When that happens, your server will never know about it and will act upon the requests. Sometimes those requests are expensive. So don't use CORS in place of any type of security.
I've been playing around with CORS recently, and I've asked myself the same question. What I've found is that the browser may be smart enough to know a spoofed CORS request when it sees one, but your server isn't as smart.
The first thing I found was that the Origin
header is an HTTP forbidden header name that cannot be modified programmatically. Which means you can modify it in about 8 seconds using Modify Headers for Google Chrome.
To test this, I set up two Client domains and one Server domain. I included a CORS whitelist on the Server, which allowed CORS requests from Client 1 but not from Client 2. I tested both clients, and indeed Client 1's CORS requests succeeded while Client 2's failed.
Then I spoofed Client 2's Origin
header to match Client 1's. The Server received the spoofed Origin
header, and successfully passed the whitelist check (or failed if you're a glass-half-empty kind of guy). After that, the Server performed dutifully by consuming all the resources that it was designed to consume (database calls, sending expensive emails, sending even more expensive sms messages, etc.). When that was done, the server happily sent the spoofed Access-Control-Allow-Origin
header back to the browser.
The documentation I've read states that the Access-Control-Allow-Origin
value received must match the Origin
value sent in the request exactly. They did match, so I was surprised when I saw the following message in Chrome:
XMLHttpRequest cannot load http://server.dev/test
. The
'Access-Control-Allow-Origin' header has a value http://client1.dev
that is not equal to the supplied origin. Origin http://client2.dev
is therefore not allowed access.
The documentation I read doesn't seem to be accurate. Chrome's network tab clearly shows both the request and response headers as http://client1.dev
, but you can see in the error that Chrome somehow knows the real origin was http://client2.dev
and correctly rejects the response. Which doesn't matter at this point because the server had already accepted the spoofed request and spent my money.