top | item 30320113

CORS is not meant to secure an API endpoint

220 points| Yabasta | 4 years ago |nikofischer.com

154 comments

order
[+] goto11|4 years ago|reply
CORS is confusing to understand because the kind of attack it protects against is confusing.

CORS does not protect endpoints against malicious clients, since you can always just make the same request outside of a browser. And it doesn't protect any site from making or receiving cross-site requests, since CORS can always be disabled on the server side.

CORS protect against the scenario where a malicious site tricks an unmodified browser to make a cross-site request to a legitimate site. If the user has an authentication cookie for the legitimate site, the cookies will be sent along with the request. So the malicious site can perform transactions in the legitimate site on behalf of the user, despite not having direct access to the authentication cookie.

CORS is further complicated because certain forms of cross-site requests have always been allowed by browsers, and therefore must remain enabled by default for backwards compatibility. GET requests to separate sites is allowed, since this has always been allowed e.g. to embed images from other domains. POST requests are allowed with the caveat that you can't inspect the result, because you have always been able to initiate a post to a different site from a html form.

[+] JanSt|4 years ago|reply
Again, CORS does not protect, the SOP does :-)
[+] EGreg|4 years ago|reply
I know the official line is that CORS “protects” the relying party (the website that initiates requests) but in practice I have found it to be quite the opposite.

From having to deal with CORS over the years, to me is just a tool to protect one thing… the intellectual property of whatever site we are loading resources from.

For example, images arrive “tainted” and you can’t get their pixels on a canvas no matter how hard you try. You can’t generate a “screenshot” you can print, and so forth.

If this was just about protecting the relying party website, that website would have a way to get around this “protection” to get something done.

Sure, people can try to get at the intellectual property directly, but they’d have to have an authenticated account first. If they do it on the site where it’s hosted, they are subject to its rules. If they are coming from some random site, eg loading it in bulk, the resulting info they get can’t be analyzed by the Javascript.

Yes CORS is just a relaxation of SOP, which makes it impossible to get sensitive data that would have otherwise been revealed to the relying party. Again — SOP is protecting the user’s data loaded from the ORIGIN. Its main job is’t “protecting the receiving site”, that’s a pretty disingenuous characterization.

[+] systemvoltage|4 years ago|reply
> CORS protect against the scenario where a malicious site tricks an unmodified browser to make a cross-site request to a legitimate site. If the user has an authentication cookie for the legitimate site, the cookies will be sent along with the request. So the malicious site can perform transactions in the legitimate site on behalf of the user, despite not having direct access to the authentication cookie.

Can you please give an example of this?

[+] JimDabell|4 years ago|reply
> CORS is an implementation in the browser and is designed to protect the user from malicious applications by ensuring that the resource in the browser is only allowed to access specific endpoints.

> This browser implementation can be bypassed at any time. First, it is up to the browser itself: if CORS is not integrated, or not integrated cleanly, then it will not work.

> An attacker can access the API key via the source code of the web app and use it, for example, via a cURL request to directly access the API resources of the backend. With cURL, no CORS takes effect, so the attacker has direct access with the full rights of the user.

All of this is completely wrong.

CORS does not protect anything from anyone. The same-origin policy stops code from one site reading resources from another site. CORS selectively removes that protection – it decreases security.

If CORS is not integrated, the same-origin policy is in full effect and code from one site cannot read resources from your site. It starts off as secure, and stays secure.

If you use cURL, then the same-origin policy doesn’t apply, and you can of course read any resource.

The same-origin policy is not a generic access barrier, and shouldn’t be used as such. It stops one site from abusing the user’s authenticated state with other sites, and that’s it.

[+] fivea|4 years ago|reply
> CORS does not protect anything from anyone. The same-origin policy stops code from one site reading resources from another site. CORS selectively removes that protection – it decreases security.

This comment comes off as either disingenuous or needlessly contrarian, and in the process tries to make points that fall somewhere between completely wrong and miopic.

Your personal assertion that CORS somehow decreases security is based on the patently false assertion that at any point in time requests only came from the same origin. Not only was this never the case, this ignores the fact that with the popularity of REST and SPAs and microservices and API-as-a-service, the norm was since switched to having browsers make requests to anything other than the same origin.

So, without CORS, you have a all-or-nothing security model, where the needle would always pend to the "nothing" side. With CORS, that needle can point "all that matters", which is pretty close to the optimal same-site solution. What CORS does is allow developers to abstract away the definition of "same origin" to mean "the origins that I explicitly allow".

You simply cannot claim that allowing only requests to the origins you explicitly allow is an erosion of a security model. That makes no sense. It made no sense when implicitly that list was only comprised of your own domain, it makes no sense now when that list includes reputable APIs that you pay to use.

[+] djm_|4 years ago|reply
From experience, I can tell you that many people simply refer to this entire domain as CORS despite that S standing for Sharing. The Same Origin Policy is treated verbally more like the default state of CORS in some circles.

It is very confusing and I’m not entirely sure how it ended up like that.

[+] goto11|4 years ago|reply
It is more complex than that, because certain kinds of cross-site requests has always been allowed. GET and POST requests are allowed, but PUT and DELETE is not. For POST requests you can send the request but not access the result. So CORS can be used to increase protection, by disabling cross-site POST requests, but it can also be used to decrease protection for other requests.
[+] winwiz|4 years ago|reply
I agree, the author doesn't seem to grasp the fact that CORS always waters down security. That is, CORS is the way of security weakening. Although the author correctly notes that only browsers respect CORS, there is no mention that this happens because CORS relaxes security built into browsers.
[+] glintik|4 years ago|reply
Of course. Author just missed how CORS works :).
[+] 2bitencryption|4 years ago|reply
Ah, CORS. One of those rare topics to fall under the category of: "I have no idea how it works. I run into an issue with it. I spend all day researching, reading documentation, blog posts, etc, explaining it. I finally come to some understanding. Three weeks later, start the process again."

To my weak-minded brain, the sticking point always comes down to: CORS/SOP is a policy enforced by the browser, correct? The client. The endpoint tells the client what origins should and should not be allowed to make the request. It is up to the client (browser) to decide whether to enforce this policy or not. Is that at least somewhat correct?

[+] jordanlev|4 years ago|reply
Yes. The other confusing thing about CORS is that CORS is not a protection against anything, but rather a way to UN-protect (in a restricted fashion) the browser's "same origin policy". Same Origin Policy is the protection, and CORS is the way to allow the browser to poke a hole through that in a limited fashion (only for the domains that the original site you visited say it should allow).
[+] notreallyserio|4 years ago|reply
In addition to what jordanlev said: the server tells the client that the origin the client sent is allowed. It's basically a boolean but for some reason the access-control-allow-origin header specifies an origin URL.
[+] HL33tibCe7|4 years ago|reply
Having read the original article (https://designkojo.com/post-drupal-using-jsonapi-vuejs-front...), it’s quite clear that the author doesn’t know enough about what he’s talking about to write these kinds of posts. I wouldn’t usually say this, but on security-critical topics like this, winging it just isn’t good enough. People will be misled by this article.
[+] franciscop|4 years ago|reply
Exactly. I was gonna give it the benefit of "maybe the original is JUST running the Vue.js App locally, never published anywhere, so adding the API key in the environment is fine" but no, reading the original it's clear that the author just has no idea what they are doing.
[+] numbsafari|4 years ago|reply
More importantly, he probably has a bunch of customers he’s done this to and they are all exposed.
[+] nerdponx|4 years ago|reply
Where can I learn/read about this topic? It seems like mysterious magic to me, and everyone is always saying everyone else is wrong.
[+] pictur|4 years ago|reply
Ignorance is the greatest wisdom. I would love to hear the comments of the owner of this article on many issues in person
[+] JanSt|4 years ago|reply
Even this article gets it wrong: CORS does not protect you in any way. It‘s a relaxation of the SOP! Thus it decreases security.
[+] tshaddox|4 years ago|reply
That’s oversimplified to the point of being extremely misleading. Under SOP, the way you loaded third-party data (not code) was JSONP, where (oops!) you’re actually loading third-party code but you just cross your fingers and hope the third party doesn’t deliver you any nefarious code.
[+] donohoe|4 years ago|reply
Seems both articles are wrong - but the real issue it gets correct is:

you should never hard-code an API key in this manner.

[+] politician|4 years ago|reply
The comments in this thread accurately reflect my own personal journey with CORS. Like waves on the shore, my confidence in my understanding of the protocol ebbs and flows with the orbit of the Moon.

Getting CORS right involves a couple days of forgetting what I think I know, closely reading how it works (again), writing up a custom middleware for whatever web framework I’m using that time because the OOTB middlewares always have subtle bugs (usually with the Origin header), and then essentially forgetting about it for the next few years.

[+] throw_m239339|4 years ago|reply
I don't get where the problem is.

CORS is well defined here: it ALLOWS a browser to make certain types of requests to endpoints outside the domain the browsed website comes from:

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

Because the browser follows the same origin policy for a number of API (like FETCH):

https://developer.mozilla.org/en-US/docs/Web/Security/Same-o...

Also if you are a developer, please check:

https://developer.mozilla.org/en-US/docs/Glossary/CSP

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

To add an extra layer of protection against cross site scripting. I think one of the recent data leak could have been avoided if the website implemented that very basic header all modern browsers support.

[+] rhdunn|4 years ago|reply
The thing that makes CORS annoying for me is when trying to get a local development environment up and running, or trying to get a standalone web page (e.g. game) running locally. For the latter, you can run a simple web server but its another added impedance that reduces usability in favour of security, like not being able to render an XML file with an xsl preprocessing statement to a local XSLT file.
[+] paradite|4 years ago|reply
CORS is primarily a concern for frontend engineers, though its implementation depends on backend. In my experience, many backend engineers struggle to understand it because it's not their concern.

I found comparing CORS with CSP helpful in understanding both concepts: https://stackoverflow.com/a/50726191/1472186 (I wrote the answer)

Also, like others have pointed out, CORS or CSP depends on the client (browser) to enforce it. So it doesn't protect against attacker with customized clients.

[+] outsidein|4 years ago|reply
What is missing is a guide how to replace the bad pattern bey a good one. Add a function that the user can login, create his individual API key, and store in a a secure way on his client (e.g. any credential store)
[+] awill88|4 years ago|reply
Agreed. This is a good article, written well for the intended audience—which I would qualify as occupying that ambiguous space between junior and senior developer knowledge. Perhaps the author hasn’t really arrived at the “right” answer for themselves. You hit the nail on the head, the solution is to login (authenticate).

Maybe the author is being careful not to offer an abstract use case as that might ironically be misinterpreted and be counterproductive? I feel security blogging is just one of those subjects where it’s more useful and prudent to express what shouldn’t be done then should. Maybe it’s okay it doesn’t include the solution, that’s left up to the reader to infer.

[+] JanSt|4 years ago|reply
httpOnly secure same-site cookie.

Even storing a session token in localStorage is not a problem if you‘re protected against xss (if your not, nothing else will protect you anyway)

[+] egberts1|4 years ago|reply
CORS is about making the web browser behave in a certain manner, and not the web site, and not the end-user.

Once fully understood, it is useless mechanism of security theater.

[+] jefftk|4 years ago|reply
It is not useless, because there is information that the browser has and that it is trusted to protect. Consider a server that returns JSON in response to a POST request, and what JSON it returns depends on the user's cookie. Properly configured CORS headers allow the JSON to be read by some origins and not others.

An attacker with curl will not have the user's cookie and an attacker with a malicious website will not have the right origin.

[+] EGreg|4 years ago|reply
The author misses one very important thing: The user only logs in on user-agents they trust.

CORS is supposed to secure the user’s data. You are NOT supposed to send global server-side data (like secret keys to third party services) through CORS.

Consider that any user data shown “publicly” to all other authenticated users (eg user icon via Facebook’s API) can be used to deanonymize that user, because someone can just create a fake account, exfiltrate the images, and do a reverse image search.

But the author is right, CORS is just one part of the equation. Together with SRI, they can definitely make secure cross-chain interfaces.

The actually insecure alternative back in the day was JSONP. Read my stackoverflow answer from OVER 10 YEARS AGO: https://stackoverflow.com/a/5447005

[+] nobodyandproud|4 years ago|reply
The real problem that someone had to urgently explain any of this in the first place.
[+] layer8|4 years ago|reply
Since, according to the article, the API key is user-specific, and presumably only a logged-in user would receive the key in the JS source, how would an attacker get hold of it? Just trying to understand the attack vector.
[+] Aeolun|4 years ago|reply
The author of the original article exported their personal key, and is now using it for everyone on their Vue website.

Presuming that only a logged in uses sees it is the problem. Everybody and their dog sees it, and can impersonate the person whose key it was.

[+] whakim|4 years ago|reply
This is why I think it's smart to discourage most developers from rolling their own authentication. Auth has gotten a bit more complex with the rise of SPAs plus API backends (like the setup in the original article), and mistakes you make with auth will bite you much harder. I'm not saying you should never do it, just that we all make mistakes and I'd rather those mistakes happen in a less security-critical context.
[+] valzam|4 years ago|reply
If my API doesn't use cookies, is there any reason for me to not fully enable CORS on the server? I.e. using JWT in a typical SPA <-> API scenario.
[+] jesstaa|4 years ago|reply
If you have any unauthenticated routes that you don't want arbitrary websites calling.

> using JWT in a typical SPA <-> API scenario. Is this typical? It's a pretty horrible setup. Cookies have a lot of great features that 'store a JWT in LocalStorage' just doesn't have.

[+] hsbauauvhabzb|4 years ago|reply
Why the hell does CORS only support exactly one origin domain or wildcard? Such a silly decision to not allow comma separated values or wildcard subdomains.
[+] blurker|4 years ago|reply
I get why you think it's silly. I'm pretty sure I thought that too when I first encountered it. But I think it makes sense and there is actually a way to support more than one but not wildcard. Multiple origin domains are supported by using the pre-flight request to change the one domain returned based on what is in the pre-flight request.

I don't know what the actual reason was, but I would guess the design decision was made so the multiple domains use case would be infinitely scalable. Otherwise you would run into header length limitations. Imagine trying to fit 10,000 domains into the header! Eventually you'd need something like this implementation anyways.

[+] hirako2000|4 years ago|reply
although browsers only accept a single origin domain or wildcard as cors header, the server can trivially handle the multi domain use case. on preflight request, check the location header in the request, and if it belongs to a defined whitelist, set the cors header on the response to that domain or subdomain. I don't know the why, but I can imagine that since there is a reasonable work around, browser implementers perhaps kept things simple and never bothered adding support for a list of specific domains.
[+] cerved|4 years ago|reply
It's inconvenient for sure but I've never dug deep into this the issue.

My guess is that it's related to subdomains not guaranteeing to be from the same origin.

The result is that people run into errors, Google them, copy paste the top SO result which advices to allow *

Doesn't seem ideal

[+] pshc|4 years ago|reply
Good question... most likely an oversight, or maybe it's to discourage lazy server operators from packing every response's headers with 50 origins.