- You visit evil.com.
- Evil tries to makes an HTTP request to bank.com/transfer.php
- But before the request goes through the Browser says, hey, wait a minute I first need to make sure if evil.com can access bank.com
- Browser asks bank.com, if evil.com (also known as origin) is allowed by sending an "OPTIONS" request first (this is before making the actual request)
- Bank.com replies in header saying "sure, everyone is allowed" (e.g. my bank), technically by setting a HTTP "Access-Control-Allow-Origin: * " header (or just evil.com instead of * )
- Browser is happy, it makes the requests and you get the data
Of course there is more to this like the server can tell which HTTP methods evil.com can do like "POST", "GET", "PUT", etc using "Access-Control-Allow-Methods". If the browser needs to send the cookies too with the request ("Access-Control-Allow-Credentials"), etc. But that is pretty much the gist of it as I understand.
Very naively, I don't understand why evil.com origin was allowed to make any request to other domains using any cookies/session/identity of the browser/user in the first place.
Why was this accepted standard? I was blown away when I first learned that a request made to xyz.com while in a browser tab showing abc.com would actually complete the request using my identity on xyz.com.
But there are valid situations where you want a request from 1 domain to be made to other domains. This is where CORS comes in.
CORS is a mechanism to loosen security, not increase it. It allows a server to say, these are the domains (outside my own domain) who can make requests. CORS headers should be set carefully so that you are only allowing the domains that should be allowed through.
Save a rather short-but-impactful list of exceptions.
> CORS is a mechanism to loosen security, not increase it.
Would that everyone shared your understanding.
Add in these two insights to those we are enlightening:
* CORS is enforced by the browser, so no, your curl command working doesn't say your service is fine
* That error message in the browser about 'no-cors'? It is 99% likely that no-cors is NOT what you want, so the error message is just misleading and unhelpful
...and you'll have covered my CORS wishlist :)
Or we could call it CORB instead (Cross origin request blocking), and then we see it's a mechanism to tighten security. Since fundamentally, what we have is an agreement against major web browser vendors that blocks cross origin requests unless the web server authors have used CORS.
I mean, how many people have encountered a problem with CORS? Almost no-one, and those that have encountered a problem with CORB and solved it by enabling a shitty CORS that opened the doors. (At least, they're fixing security holes in software that was written by devs who encountered a CORB problem and fuxed it. But all CORS problems follow a CORB problem.)
If we called it by its true name, maybe it would help people understand what's happening. Names are important. If developers understand CORB, they will potentially understand CORS. But no-one can understand CORS till they've understood CORB.
Cross origin requests are allowed (as long as they're simple). Reading the response is what's blocked.
Thus, cross-origin POST requests were allowed, and changing it was impossible without breaking the web. Likewise you can include images or iframes of other origins, allowing GET requests. However, you cannot read the responses! The JavaScript APIs just make it more convenient to make these requests, but they don't change what you can do - you still can't read the response. Not allowing it in the JS API would just make the attack more annoying (fall back to the legacy methods) and limit legit use cases.
It's important that you are not allowed to make arbitrary requests: As soon as you start using advanced features like custom headers (in other words, things you couldn't do with img tags, redirects, forms, and iframes), you run into preflight (where the browser asks the server whether the request is allowed before performing it, via OPTIONS, i.e. in a way that legacy servers will not understand and safely reject).
This is a compatibility feature that can never change, and shouldn't change, because we can't break the web. But my reading is that CORS is mostly a band-aid on what was a bad security policy to begin with.
The problem isn't that a browser might let a web page on a separate domain send and read any arbitrary request to another domain without permission -- with the exception of DDOS and bot attacks against servers, it's difficult to think of what risks this would actually pose to users themselves. And any native program on your computer can already send an arbitrary request to an arbitrary domain and read the data -- so there are at least few arguments that it might even be beneficial for websites to be able to do the same.
The security problem is that requests are made with your current cookies regardless of what domain you're on. Ideally, browsers from the start would have isolated every domain into their own container, so that if evil.com made a GET/POST request to mybank.com, it wouldn't be authenticated as me, regardless of how the server was configured.
We made it so that cookies could only be read by certain domains, but we didn't think until very recently to make it so that cookies could only be sent by certain domains. And at this point, too much infrastructure relies on this behavior for us to ever come up with a better model, so we're stuck with hacks like CSRF tokens which are dependent on websites being coded well.
That's not fully correct. Even without cookies it's bad if a.com can read a response from b.com . Why? Because b.com might be hosted on a private network, and contain private information. We don't want a.com to steal that information. Even if cookies aren't sent, there can be other types of ambient authority, and connectivity to a private network is one of those types of ambient authority. Another similar type is if a website is protected using a whitelist of user IPs. Side note: any website that uses this type of ambient authority that isn't associated with a hostname must also check the Host header to protect against DNS rebinding.
Of course if the web was built from the start such that a.com can read b.com just without cookies, then such websites that rely on non-host-associated ambient authority (hopefully) wouldn't be built and you would be right.
Just because a native program can do something doesn't mean a website should be able to. A native program can delete all my files without asking me. A website can't.
As an extreme example, if I were to stream video to your browser from my website, evil.com, I would have to either host or proxy those streams through my server. Instead, I can just point your browser to a video from youtube.com or something.
If youtube.com then decides you need to log-in (or be logged-in) to verify your age (as they do), then that is between your browser and youtube.com to negotiate -- evil.com never sees your credentials.
This is probably the reason that any url that makes changes to user data like logout.php, transfer.php, etc must be a POST request (with csrf token to protect again requests created via form submissons)
Also it's still possible to bypass CORS using JSONp requests or for submisson, but for that transfer.php must support jsonp too (by setting the callback method)
EDIT: Why isn't there a header that web servers can give out to browsers basically saying "Don't use the cookie/session I'm giving you ever, unless you're literally on this site"? I could see this being very useful for banks or other origins where they expect no requests except from direct users.
You can't make POST requests with ajax as CORS will protect you, but evil.com can create a fake form submission (with bank.com/transfer.php as the form's action) which can be a POST request. So in this case you match the csrf token which only you know and which is unique for the form/session.
It's a rabbithole :)
Try it yourself: https://news.ycombinator.com/item?id=20406633
There is, it's relatively new though:
https://blog.mozilla.org/security/2018/04/24/same-site-cooki...
- You visit evil.com
- Evil tries to make an HTTP request to bank.com/transfer.php
- The browser happily performs the request, authenticated with your cookies, and the bank, having a CSRF vulnerability, happily sends your money to the attacker.
- Since 'evil.com' and 'bank.com' are different origins, Browser refuses to provide the response to evil.com, but the attacker doesn't care, he got the money.
CORS allows you to relax these restrictions, not tighten them.
Now, bank doesn't like these attacks. So they make the legitimate application send an additional custom header, "X-Totally-Secure: true". Despite being a really bad idea, if (big if) the browser follows the standards, this prevents the attack:
- evil.com tries to make the HTTP request as before
- Browser lets it through, as before
- Bank rejects the request because it's missing the magic header
So the attacker adds the header to the request:
- evil.com tries to make a non-standard HTTP request to bank.com/transfer.php, with the header attached
- BECAUSE IT'S A NON-STANDARD REQUEST, browser asks bank.com (as you described, OPTIONS)
- Bank.com replies "wtf do you want I don't know what OPTIONS is"
- Browser refuses to make the request
Unfortunately, the bank forgot that they have a marketing department, that runs ournewbankapp.com, and shows your current balance in the fake screenshot of the app to show how awesome it is. And your bosses' bosses' boss has yelled at the IT department that rolled out the security measure to make it work again. They make ournewbankapp.com send the magic header (including access-control-allow-credentials), but now the OPTIONS request fails. So they teach the web server to respond with "everyone is allowed" (with "access-control-allow-origin: *" as you described) because they're lazy and dumb.
But because browser vendors know that developers are lazy and dumb, the browser completely ignores this: If access-control-allow-credentials is set, the allowed origin must be listed explicitly. The developers give in, and explicitly add ournewbankapp.com to the header, and now it works, but the attack doesn't work.
(part 2 follows)
Also glad I didn't mention jsonp. God knows what the devs at ournewbankingapp would do with that one ;)
- Evil tries to make an HTTP request to bank.com/transfer.php
If this is a regular HTTP POST (ie submitting a form, and the browser window changes location), the browser will allow it.
If this is an xhr POST, the browser, following the same-origin policy, allows the request, but prevents accessing the response (unless allowed with CORS).
[EDITED with tgsovlerkhgsel's help!]
document.getElementById("myForm").submit();It is possible that XHR, or common XHR libraries, default to adding some header that makes it a non-standard request, but a fetch() call works.
In Firefox, open a debug console and run
fetch('https://otherorigin.example.com', {method: 'POST', body: 'blah'})
You will see two things. In the console: Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://otherorigin.example.com/. (Reason: CORS request did not succeed).
In the network tab, a HTTP request.Replace with a URL of a server you control, or run `nc -lnvp 9999` and replace the URL with http://127.0.0.1:9999, to see that the request is indeed being made.
As the author said... few people understand CORS.
Nothing with a Cookie header for example...
r = new XMLHttpRequest();
r.withCredentials = true;
r.addEventListener('load', function(e) {console.log('loaded: ', this, e);});
r.addEventListener('error', function(e) {console.log('error: ', this, e);});
r.open('GET', 'https://www.google.com');
r.setRequestHeader('Cookie', 'somecookie=somecookievalue')
r.send();
But this will send a request with the automatically set cookies just fine: r = new XMLHttpRequest();
r.withCredentials = true;
r.addEventListener('load', function(e) {console.log('loaded: ', this, e);});
r.addEventListener('error', function(e) {console.log('error: ', this, e);});
r.open('GET', 'https://www.google.com');
r.send();
Assuming the user is already logged in to bank.com , the user's cookies will be automatically sent on the request, and the transfer will go through assuming there is no CSRF protection.- instead of hardcoding the allowed value, they set it to always echo the value of the Origin header - browsers are powerless against that much stupidity, and can't distinguish it from a correctly configured server that is allowing the request. evil.com sends the request, bank says "evil.com" is allowed, browser shrugs and sends the request.
- instead of using CORS, they could have tried to build their own custom solution that allows ournewbankapp.com. The attacker would have to analyze how it works, and would then most likely find a way to exploit it to perform the attack, while legitimate users with privacy extensions would make the support hotline despair due to countless people complaining "I can't send money".
- instead of adding the custom header, they could have decided to check if "origin" is present, and if so, assume it's a cross-site request and check the origin against a whitelist. This still relies on standard-compliant browsers but isn't the worst idea AS LONG AS YOU REQUIRE AN ORIGIN VALUE, AND TREAT LACK OF AN ORIGIN HEADER AS A FATAL ERROR, BEFORE TAKING ANY ACTION (e.g. the money transfer) BASED ON THE REQUEST. I think can force same-origin requests to include the origin header with some option on fetch(). But if you treat a missing (or "null") header as ok, the attacker will likely find a way to make some browsers send no header, and steal money again. Sending "null" (or some other default value, been a while since I played with it) is easy, I think with some iframe trickery.
- they could have used plain HTTP, exposing them to DNS rebinding attacks even from remote attackers who cannot sniff their user's traffic.
- they could have an XSS vulnerability somewhere on the main site or the whitelisted marketing site, allowing the attacker to proxy his malicious requests through that site (and thus make them use a whitelisted origin). Maybe some long-forgotten kludge designed to proxy requests to third parties that don't support CORS...
They set it to check a regex for `bank.com` to also allow `subdomain.bank.com`, but inadvertently also allow `bank.com.evil.com`, `evilbank.com`, or similar.
XSS is essentially completely separate from CORS and CSRF. XSS is a bad vulnerability, worse than CSRF, and you can't expect any type of CSRF prevention to protect against XSS.
I've been in this situation before with web-based cross-platform apps using Cordova. As far as I remember, I ended up echoing the origin domain if it matched a set of allowed domains (basically localhost and example.com), since regular expressions or lists are not supported in allowed-origin headers. For session tracking, I used tokens rather than cookies to avoid CSRF issues. Would there have been any better solution for this? I think not.
https://github.com/OWASP/CheatSheetSeries/blob/master/cheats... is probably one of the best documents discussing this, including the drawbacks of the header method that I used (and warned against) in my example:
https://github.com/OWASP/CheatSheetSeries/blob/master/cheats...
Am I missing something or is this transaction actually a browser being the deciding factor for whether or not the request gets sent?
If that’s true, couldn’t a nefarious browser decide when to push a request and completely ignore the OPTIONS header?
Both CORS and CSP are for guarding against malicious code on the web, being executed by non-malicious standards-following browsers used by non-malicious users. (the user is in fact who is being attacked).
I think is frequently confused, and it leads to a mistake in the other direction, people thinking CORS or CSP can guard against malicious user-agents operated by malicious users. It is not for that!
CORS prevents a malicious site from exfiltrating/accessing data using the access you have, for example an internal site that is on your computer's network but the malicious website's servers can't directly access.
However, this attack is most useful if you can get the victim's browser to send the request for you, because that way, you can get it to include the victim's authentication cookies.
If you as the attacker send the request yourself, you don't have the cookies.
If you make the victim's browser send it, you either can't make them use a nefarious browser, or you already won since you have code execution on the victim's machine.
No web standard can protect you from a nefarious browser, since the browser could just decide to not follow the standard.
For example, I've gotten decent at regular expressions a few times but have to keep relearning it. Adding insult to injury, I might relearn something from Stackoverflow, and realize afterwards I posted the answer!
Not for trivial stuff. But a string of punctuation with capture groups or whatever, forget about it [1].
Any support for this informal draft definition below?
------------------
Teflon tech (draft definition v0.1 [2])
Knowledge that could be learned by most with reasonable effort, but often must be relearned or is never fully grokked.
This can be due to infrequent opportunities to recall the concepts in a particular role, or insufficient need to apply (thereby practicing) the relevant techniques. It may also occur when a tech is used frequently but the foundational theory is hidden by the use of higher level abstractions.
[1] https://stackoverflow.com/questions/1381097/how-do-i-get-the...
[2] content provided under MIT license.
Probably my having a super low opinion of it's whole ecosystem doesn't help. ;)
The Javascript ecosystem is huge. It's also the wild west, completely non-curated, open to anyone, and you have to treat it accordingly. It's not really one entity.
But... sigh I feel exactly how you feel about Git about Vim. It's the one editor everyone has decided despite it's arcane and bizarre UX that you aren't a real programmer if you don't know.
Refuse to learn it. Absolutely refuse.
I think everyone has one of those things.
In the 2000s I learned Perl no less than three times. I was really impressed by what people could do with small scripts and one-liners, and I wanted that power for myself. I got really good three times, and each time it took me less time to forget it than it took me to learn it. I did not agree with its decision in that case, but I lost the argument.
I shall graciously accept this being called adtac's law out of sheer humility :P
I'm ok with regex too, but if you asked me to do something with look-behind I'd have to experiment a little to figure out how it works again.
To truly understand CORS, you have to fundamentally understand 'the origin' as a security and execution context. Sharing means that content normally intended for another origin is shared with the initial origin.
Perhaps that is blatantly obvious and you are more confused by the myriad of headers, exceptions and lifetime. In that case, I propose to read more of the spec.
Ingest more teflon when in doubt about its toxicity.
(Above 300°C Teflon will generate toxic fumes, but then again, so will wood.)
Unless you are a parrot. I've heard it is disproportionately toxic to birds. (or is it just parrots?)
And why would I coat surgical instruments?
Edit: Just one article how healthy Teflon is:
https://fortune.com/longform/teflon-pollution-north-carolina...
Anyways I'm almost always using plain old steel pans and pots. There are very few things that stick so badly I resort to the one Teflon pan I have.
[1] https://theintercept.com/2018/07/31/3m-pfas-minnesota-pfoa-p...
But stick with it. It does slowly sink in.
Something that especially confused me at first was that all of the CORS blocking happens in the browser. So you never run into CORS using curl, etc.
It allows you to make arbitrary HTTP requests across origins, even those that would normally be blocked by the same-origin policy, as long as the server collaborates and allows it, easily and in a secure way.
If you control the server (or can get someone to adjust it, or it already supports it), you can use it to make requests from one website to another, directly from the client.
For example, you can have JS on a site served on www.example.com make calls directly to api.someservice.net, without any kludges.
Any time you think of some contrived proxy solution, JSONP, or other kludges to bypass the same-origin policy, you want CORS.
For non-anonymous APIs, authentication can still be a challenge.
The important part is that CORS can be used to relax any existing same-origin restrictions on HTTP requests.
1. If the same-origin policy isn't a problem for you, no need to change anything, obviously.
2. If you want to _tighten_ the same-origin policy, CORS isn't going to help you (as far as I know).
3. You can get rid of any request-related same-origin policy restriction with CORS, easily and in a way that is easy to secure. There is no need to build around it.
4. If you try to use something custom to achieve that instead, you're likely making a dumb mistake, because you're not only creating unnecessary work, but also most likely either breaking many more users than there are users that don't support CORS, or introducing a gaping security hole, or both. Usually both.
Very little objective thought involved.
Among all the W3 specs, the CORS one is definitely not the largest spec. See https://www.w3.org/TR/cors/
Ultimately the Zoom vulnerability reminded us that there is always a trade off between usability and security. Most of the developers default to usability and simplicity.
The average developer looks for the fastest way to achieve "what I want to do". And they do not even question installing stuff with "curl https://any.domain.com/any-script | sudo bash".
And that is the root of all evils.
I’m personally at the point where I’d rather handle unnecessary request authentication than trying to do anything with CORS.