Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authentication: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' #41

Closed
m-mohr opened this issue Feb 21, 2018 · 30 comments
Assignees
Labels
bug critical patch requires a patch-version (x.x.1 for example)
Milestone

Comments

@m-mohr
Copy link
Member

m-mohr commented Feb 21, 2018

Seems we have a severe problem with Authentication:
When sending Basic Auth credentials (probably also for Bearer token), the servers are not allowed to set the Access-Control-Allow-Origin to * (wildcard).

Not sure yet how this can be solved appropriately, maybe we need to have an alternative way for logging in, e.g. sending the credentials via body or query parameters?

Some background: http://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html

@m-mohr m-mohr added this to the v0.0.2 milestone Feb 21, 2018
@m-mohr
Copy link
Member Author

m-mohr commented Feb 22, 2018

Hopefully fixed with #42.

@m-mohr m-mohr closed this as completed Feb 22, 2018
@soxofaan
Copy link
Member

Can this ticket be re-opened (or should I create a new one)?

I think the solution introduced by #42 still leaves a security issue as discussed in that blog post https://portswigger.net/research/exploiting-cors-misconfigurations-for-bitcoins-and-bounties and https://ejj.io/misconfigured-cors/:

this combination as specified in the current openeo spec is problematic:

  • Access-Control-Allow-Origin: It is RECOMMENDED to return the value of the request's origin header.
  • Access-Control-Allow-Credentials : If authorization is implemented by the back-end (so practically always) the value MUST be true.

@soxofaan
Copy link
Member

(FYI: while digging into Open-EO/openeo-python-driver#54 I stumbled onto this old ticket)

@m-mohr
Copy link
Member Author

m-mohr commented Nov 17, 2020

Unfortunately, I don't see a solution on the API side for the issue. Did you catch one? Everything that is more strict makes the JS client (including Web Editor / Mobile App / Jupyter Lab integrations) basically useless.
The only thing I could imaging doing at the moment is to add that Access-Control-Allow-Origin should be properly validated (although I'm not clearly getting from the articles how to validate) and not return null or *.
How do larger corporate APIs implement that in circumstances where the origin is not well-defined, i.e. is not a single web page?

Edit: Another unrelated thing we should probably add is that servers should send "Vary: Origin".

@m-mohr m-mohr reopened this Nov 17, 2020
@m-mohr
Copy link
Member Author

m-mohr commented Nov 17, 2020

As far as I understand the Google API implementation (related SO comment), it doesn't allow requests from browsers (i.e. no CORS implemented) and you need to use the libraries hosted at google (i.e. same origin or white-listed origin). So that means if we want to make it work safely, every back-end needs to host their own JS client or it needs to white-list a CDN hosted JS client?! Wow!

@aljacob
Copy link
Member

aljacob commented Nov 18, 2020

this is definitely an issue, and while setting this up in our environment a noticed, that there are many places where this creates issues and for sure leaves opportunities for security breaches, when working with the wildcard or not checking for CORS

@soxofaan
Copy link
Member

The only thing I could imaging doing at the moment is to add that Access-Control-Allow-Origin should be properly validated

that is also the only thing I can think of at the moment. Possible implementations:

  • new repo under https://github.com/Open-EO with whitelist of allowed origins, managed by openeo PSC. Backends can then depend on this project for their whitelist. Advantage: no need to define new (back channel) API. Disadvantage: whitelist updates don't propagate automatically and require (probably) re-deployment of each backend.
  • central HTTP service that hosts whitelist, or provides an origin validation endpoint. Advantage: whitelist updates are immediately available to all backends. Disadvantage: requires set up and maintenance of central service, single point of failure, extra caching challenges for backend, is non-trivial update to openeo API spec.
  • something in between: use github repo directly as "central service" . Disadvantage: external entity (github) is now single point of failure
  • ...

@m-mohr
Copy link
Member Author

m-mohr commented Nov 18, 2020

that there are many places where this creates issues

@aljacob Could you elaborate that a bit more?

whitelist of allowed origins

@soxofaan Is white-listing origins the only way out? I don't like any of the approaches to be honest. It makes browser-side usage a pain (includes many mobile apps) and how should the central instance (PSC?) decide who is trustworthy?

@m-mohr
Copy link
Member Author

m-mohr commented Nov 18, 2020

@jneijt How do you use the JS client at the moment? I guess this would break the mobile app as the Origin is always somehow the mobiles IP or so? Or is this centrally hosted somewhere at Solenix?

Some hosts that we'd need to whitelist:

  • openeo.org (for future use)
  • editor.openeo.org (Web Editor Production)
  • platform.openeo.org (for future use, e.g. likely for a openEO Platform branded web editor?)
  • hub.openeo.org (for future use)
  • open-eo.github.io (Web Editor Dev/Demo)
  • ... (Mobile App)
  • ... (The openEO Platform Jupyter Lab)
  • cdn.jsdelivr.net (CDN; Centrally hosted JS client we use in examples etc)
  • unpkg.com (another CDN we used before jsDelivr)

@soxofaan
Copy link
Member

is it necessary to whitelist these CDNs? as far as I understand you only have to whitelist the domain of a webapp "page" itself, not the domains of the JS code it loads.

@m-mohr
Copy link
Member Author

m-mohr commented Nov 18, 2020

That's how I understood the Google API workflow, but maybe it's indeed the site/apps origin, not the origin where the JS files are hosted. We'd need to invetigate...
In case it's the site/apps origin, it gets even more annoying as we can't even offer a centrally hosted instance of the JS client for sites/apps to use. It makes the JS client very much unusable for externals and thus makes not much sense to put a lot of effort into it...

@soxofaan
Copy link
Member

Is white-listing origins the only way out? I don't like any of the approaches to be honest ... how should the central instance (PSC?) decide who is trustworthy?

Indeed, a global, centrally managed whitelist is a pain, and it having the PSC maintain it doesn't scale well.

Ultimately it's all about the trust between a user (defined at backend side) and a webapp. So maybe it makes more sense to work with something like per-user whitelists, where a user maintains, through their backend account, a list of webapp origins it trusts. This makes webapp usage harder, but maybe we can think of a flow that doesn't raise the bar too much (e.g. bootstrap the whitelist with some defaults like editor.openeo.org).

Advantages:

  • no need for central whitelist
  • developers don't have to go through official procedure to whitelist a new/external app

Disadvantages:

  • more user related bookkeeping to do by backend
  • additional flows to implement in clients (e.g. forward to whitelisting form on backend when first using webapp)
  • API spec about CORS will be more complicated because CORS response headers will depend on whether request included credentials
  • maybe a chicken and egg problem around authentication: CORS will depend on credentials, so is it possible to authenticate in first place?
  • we move security related responsibilities to the end user
  • by inventing our own trust mechanisms we risk introducing security issues we didn't think of
  • if user uses multiple backends: multiple whitelists to maintain

Lot of disadvantages, so not a very likeable alternative either 😟

@m-mohr
Copy link
Member Author

m-mohr commented Nov 18, 2020

I guess we would need both: User-defined origins and to get started a list of pre-defined origins that originate directly from within the openEO consortium. I don't see an alternative. (Unfortunately, I still don't completely understand from reading the articles what the exact security issue is. Can someone explain that?)

@m-mohr m-mohr modified the milestones: v0.0.2, v1.0.1 Nov 18, 2020
@soxofaan
Copy link
Member

https://ejj.io/misconfigured-cors/ explains it somewhat better than the other blog post:

  • you are logged in with your browser on editor.openeo.org, using backend backend.com
  • you visit with the same browser a shady website evil.com
  • evil.com has JS that does requests in the background to backend.com allowing it steal data (tokens, api keys, ...) because you are logged in (e.g. through cookies)

That's why it is important for backend.com to validate the origin in some way (e.g. whitelist or another back channel)

But now that I write down this flow, I realize that we might workaround the problem. The attack is possible because the browser automatically sends cookies along with the request from evil.com. But in case of openEO, we don't use cookies for authentication, but (somewhat custom) authentication headers that are set explicitly by the client code. So if we can specify that Access-Control-Allow-Credentials MUST be false (instead of MUST be true) , the problem might be solved without whitelisting.
At the moment I'm not completely sure if Access-Control-Allow-Credentials is only about cookies and not about the authentication headers we currently send, that requires some more reading and testing I guess.

@jneijt
Copy link

jneijt commented Nov 18, 2020

@m-mohr I still have to read up on the articles you referenced but to quickly answer the question about what the mobile app does:

  • It has a copy of the JavaScript client included in the app (doesn't load it from anywhere)
  • As far as I know, it uses localhost:4200 as origin when requesting anything on the backend (development for sure, I thought this is true for the production one as well).

And on what @soxofaan said:

Ultimately it's all about the trust between a user (defined at backend side) and a webapp. So maybe it makes more sense to work with something like per-user whitelists, where a user maintains, through their backend account, a list of webapp origins it trusts. This makes webapp usage harder, but maybe we can think of a flow that doesn't raise the bar too much (e.g. bootstrap the whitelist with some defaults like editor.openeo.org).

Isn't that what Google does with it's APIs as well? When requesting an API key, you can/should set the allowed origins for the application you are going to use the API from. I think this is a pretty common approach.

@m-mohr
Copy link
Member Author

m-mohr commented Nov 18, 2020

https://ejj.io/misconfigured-cors/ explains it somewhat better than the other blog post
[...]

  • evil.com has JS that does requests in the background to backend.com allowing it steal data (tokens, api keys, ...) because you are logged in (e.g. through cookies)

I've read that yesterday, too, but I'm still not sure it applies for us. How can someone evil making requests to backend.com grab information that he shouldn't get like the Authorization Header (he'd need to send, right?) or access tokens in responses? We don't have cookies that could be passed. Does it have something to do with the browser cache? And how does this "Vary: Origin" header affect that?

So if we can specify that Access-Control-Allow-Credentials MUST be false (instead of MUST be true)

AFAIK We can't do that because the header "Authorization", which we send the Basic and Bearer tokens with, can only be used in JS if Access-Control-Allow-Credentials is set to true.

Credentials are cookies, authorization headers or TLS client certificates.

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
But as I said, not sure how the Authorization header is then exposed to evil.com...

@m-mohr
Copy link
Member Author

m-mohr commented Nov 18, 2020

  • As far as I know, it uses localhost:4200 as origin when requesting anything on the backend (development for sure, I thought this is true for the production one as well).

Would localhost make problems with the stricter approach? I'm not sure...

Isn't that what Google does with it's APIs as well? When requesting an API key, you can/should set the allowed origins for the application you are going to use the API from. I think this is a pretty common approach.

I know you need to specify redirect URIs for OpenID Connect, but I'm not aware that you can specify CORS Origins... anyone has details? Can only find information for cloud buckets, not for APIs.

is it necessary to whitelist these CDNs?

Doesn't seem to be the case. I just tested it and it is NOT using the host of the JS file, but the host of the requesting web page as origin. Then I don't understand yet how Googles APIs work...
Edit: Actually, https://de.tenable.com/blog/understanding-cross-origin-resource-sharing-vulnerabilities says you should not allow CDNs.

@jneijt
Copy link

jneijt commented Nov 18, 2020

I know you need to specify redirect URIs for OpenID Connect, but I'm not aware that you can specify CORS Origins... anyone has details? Can only find information for cloud buckets, not for APIs.

Isn't this done by specifying "HTTP restrictions" on the API key? It's called differently, but I think it's basically the same idea. I might be completely wrong here though...
https://cloud.google.com/docs/authentication/api-keys#adding_http_restrictions

Would localhost make problems with the stricter approach? I'm not sure...

Which stricter approach do you mean? AFAIK it has to be added to the allowed origins, just like any other URL like editor.openeo.org.

@m-mohr
Copy link
Member Author

m-mohr commented Nov 18, 2020

As far as I understand https://www.sjoerdlangkemper.nl/2018/09/12/authorization-header-and-cors/, Authorization headers are attached automatically to requests in some cases (i.e. Basic Auth), so that users don't need to input the credentials each time the click on a new page. This would indeed expose credentials to evil.com, but in our case I hope this is not the case and Bearer Tokens are not sent automatically. Still, it would break the security of /credentials/basic, but the API clearly says it is not meant to be used for production so I'd be fine with that.

Edit: SO question on which Authorization schemes (e.g. Basic, Bearer, ...) are sent automatically by browsers, unfortunately no response yet: https://stackoverflow.com/questions/15427650/when-is-the-authorization-header-automatically-sent-by-the-browser

Isn't this done by specifying "HTTP restrictions" on the API key?

In the English version it explicitly names it "HTTP referrers". Does this refer to HTTP Referrers (i.e. header Referer) or the HTTP Header Origin or both? If it's just HTTP Referrers, it doesn't apply for our use case here.

Which stricter approach do you mean?

White-listing specific domains instead of just reflecting Origins.

Current status for changes in 1.0.1:

  • Vary: Origin header must be set to avoid cache issues
  • Add that Access-Control-Allow-Origin should never return "null" or "*" (and be validated?)

@jneijt
Copy link

jneijt commented Nov 18, 2020

White-listing specific domains instead of just reflecting Origins.

As long as localhost is whitelisted, it should be fine. But I don't know what implications whitelisting localhost has.

@m-mohr
Copy link
Member Author

m-mohr commented Nov 18, 2020

As long as localhost is whitelisted, it should be fine. But I don't know what implications whitelisting localhost has.

Yeah, if * is an issue, localhost should be black-listed as everyone could send requests from localhost, right? Maybe I'm missing something though...

@jneijt
Copy link

jneijt commented Nov 18, 2020

That's my feeling as well, exactly...

@jneijt
Copy link

jneijt commented Nov 18, 2020

Ok, it looks like the Origin is a matter of configuration for the mobile app. By default it uses ionic://localhost on iOS and http://localhost on Android but both the scheme and the hostname can be configured according to the documentation (https://github.com/ionic-team/cordova-plugin-ionic-webview#hostname).

I haven't tested this but it would solve the localhost issue for the mobile app as we could define some origin to use for the app.

@m-mohr
Copy link
Member Author

m-mohr commented Nov 18, 2020

@jneijt Could you specify a distinct domain name in the app for the next release so that if we decide to implement white-listing later, we can easily add the domain? Either some solenix domain name or something like https://demo-app.openeo.org ... Might not be required... see below.

Regarding the security of HTTP Basic, it seems some valuable information can be found here: https://tools.ietf.org/html/rfc7617#section-2.2
I couldn't find anything similar in the Bearer RFC yet and I doubt it's in there as it actually is written for OAuth: https://tools.ietf.org/html/rfc6750

SO question on which Authorization schemes (e.g. Basic, Bearer, ...) are sent automatically by browsers, unfortunately no response yet: https://stackoverflow.com/questions/15427650/when-is-the-authorization-header-automatically-sent-by-the-browser It seems though his experiments lead to the conclusion Bearer Token are not sent automatically by browsers. So maybe we are safe?

@m-mohr m-mohr self-assigned this Nov 18, 2020
@soxofaan
Copy link
Member

soxofaan commented Nov 18, 2020

How can someone evil making requests to backend.com grab information that he shouldn't get like the Authorization Header (he'd need to send, right?) or access tokens in responses? We don't have cookies that could be passed.

Indeed, that is also my current understanding of the attack: it depends on the browser to automatically include backend.com cookies with the requests done from evil.com. As far as I know there are no browser mechanisms that automatically include authorization headers to JS-based requests in the same way. So the security issue might not apply to openeo API (as long as we don't use cookies).

As far as I understand https://www.sjoerdlangkemper.nl/2018/09/12/authorization-header-and-cors/, Authorization headers are attached automatically to requests in some cases

Ok interesting, so there are mechanisms to automatically include authorization headers. Not sure if this applies to requests done from JS as well.

So if we can specify that Access-Control-Allow-Credentials MUST be false (instead of MUST be true)

AFAIK We can't do that because the header "Authorization", which we send the Basic and Bearer tokens with, can only be used in JS if Access-Control-Allow-Credentials is set to true.

I'm not sure here. The client indeed sends the tokens through headers to the backend, but it does not receive them from the backend:

  • for basic auth, the token is in the body
  • for OIDC, the token comes from the OIDC provider, not the backend

so we don't need to enable Access-Control-Allow-Credentials=true I think.

I've been playing a bit with that demo page of https://www.sjoerdlangkemper.nl/2018/09/12/authorization-header-and-cors/, and it seems that it indeed works without Access-Control-Allow-Credentials

@m-mohr
Copy link
Member Author

m-mohr commented Nov 18, 2020

@soxofaan Interesting, from reading the page it seems that the header is required if you want to send Authorization headers (that's how I understand it), but the demo page indeed doesn't require me to add the Credentials flag... strange, I'm pretty sure I struggled with this in the JS client. I'll so some experiments tomorrow with GEE and JS.

@m-mohr
Copy link
Member Author

m-mohr commented Nov 18, 2020

Okay, looking into the code of the demo page and reading some additional docs enlightened me! (I hope I understood it correct.)

I need to disable withCredentials: true (the same as credentials: include in fetch) in the JS client, but still send the Authorization header! I thought withCredentials would be required to send the Authorization header, but it doesn't seem to be the case. I'll check that tomorrow, but in that case we can completely omit the Access-Control-Allow-Credentials header and just specify * for Access-Control-Allow-Origin. That makes it sooooo much easier!

@jneijt Forget my request above for now...

All this might be going into a 1.0.1 release with PSC vote before.

@m-mohr
Copy link
Member Author

m-mohr commented Nov 19, 2020

It works like a charm without the credentials headers etc. So I'll make PRs, push it through PSC if required etc. So that I hope to get an API 1.0.1 out soon. Thanks for pushing me on this, @soxofaan!
This clearly shows how bad documentation can influence a whole project.

@jneijt I'll also issue a fix for the JS client, so better wait a day with the release so that you can get in those changes.

@jneijt
Copy link

jneijt commented Nov 19, 2020

@m-mohr Great to hear, thanks! I'll wait until the new version is available ;-)

m-mohr added a commit to Open-EO/openeo-earthengine-driver that referenced this issue Nov 19, 2020
m-mohr added a commit to Open-EO/openeo-js-client that referenced this issue Nov 19, 2020
@m-mohr
Copy link
Member Author

m-mohr commented Nov 19, 2020

Two PRs open: Open-EO/openeo-js-client#38 and #345

m-mohr added a commit to Open-EO/openeo-web-editor that referenced this issue Nov 19, 2020
@m-mohr m-mohr added the patch requires a patch-version (x.x.1 for example) label Nov 19, 2020
m-mohr added a commit that referenced this issue Nov 20, 2020
Fix security-related CORS issues regarding to credentials #41
@m-mohr m-mohr closed this as completed Nov 20, 2020
soxofaan added a commit to Open-EO/openeo-python-driver that referenced this issue Nov 30, 2020
- Implement API 1.0.1 CORS recommendation (Open-EO/openeo-api#41)
- Address both OPTIONS and non-OPTIONS requests properly
- Fixes #54, #55
- add more unit tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug critical patch requires a patch-version (x.x.1 for example)
Projects
None yet
Development

No branches or pull requests

4 participants