Rob--W / cors-anywhere

CORS Anywhere is a NodeJS reverse proxy which adds CORS headers to the proxied request.
MIT License
8.61k stars 6.02k forks source link

Add option to set Access-Control-Allow-Origin to echo to request origin #55

Open cleavera opened 7 years ago

cleavera commented 7 years ago

When using CORs with Access-Control-Allow-Credentials:true the Access-Control-Allow-Origin cannot be *. Usual practice is the echo back the the request origin as the allowed origin. It would be good to have some option to do this rather than having to know the origin ahead of time and add it to the whitelist.

Rob--W commented 7 years ago

PR #52 adds the option to reflect the origin.

Note that CORS Anywhere should not be used with Access-Control-Allow-Credentials: true, because it would allow other (proxied) domains to see the cookies and other credentials of other proxied domains.

catamphetamine commented 4 years ago

@Rob--W

Note that CORS Anywhere should not be used with Access-Control-Allow-Credentials: true, because it would allow other (proxied) domains to see the cookies and other credentials of other proxied domains.

It's not clear what you meant there. There seems to be no reason to not set Access-Control-Allow-Credentials: true which requires Access-Control-Allow-Credentials not being equal to * but instead set to request.headers['Origin']. https://fetch.spec.whatwg.org/ Otherwise cookies won't work.

My fix is to edit ./lib/cors-anywhere.js and replace headers['access-control-allow-origin'] = '*' with headers['access-control-allow-origin'] = request.headers['origin'] and add headers['access-control-allow-credentials'] = true; after it. This seems to fix cookies when using fetch({ mode: 'cors', credentials: 'include' }).

Rob--W commented 4 years ago

@catamphetamine Cookies by browsers are send to a specific domain. When you enable credentials on CORS Anywhere, the cookies set by one proxied site are sent to every other proxied website, because every request from the browser goes to the CORS Anywhere domain. This is a huge security issue, and therefore I strongly discourage the use of credentials with CORS Anywhere.

The only situation where your proposal is acceptable is when your CORS Anywhere proxy is configured to only accept requests to one destination. But that requires more efforts, in particular you need to account for redirects (and strip cookies on redirects, OR not automatically follow redirects).

catamphetamine commented 4 years ago

@Rob--W

Cookies by browsers are send to a specific domain. When you enable credentials on CORS Anywhere, the cookies set by one proxied site are sent to every other proxied website, because every request from the browser goes to the CORS Anywhere domain. This is a huge security issue, and therefore I strongly discourage the use of credentials with CORS Anywhere.

Hmm, I see, so all cookies for multiple proxied domains would end up being set on "CORS Anywhere" domain. That's a security issue indeed. Still, if such "CORS Anywhere" instance is configured to only allow proxying a certain origin, there would be no risks involved because an attacker couldn't make it forward HTTP requests to their site to read the cookies.

For example, where you have:

var location = parseURL(req.url.slice(1));

Some kind of an isAllowedOrigin(location.origin) function call could be added (along with the setting).

Another protection from this kind of attacks is configuring originWhitelist so that an attacker would be denied access to a "CORS Anywhere" instance because they can't host a page at such origin.

The only situation where your proposal is acceptable is when your CORS Anywhere proxy is configured to only accept requests to one destination. But that requires more efforts, in particular you need to account for redirects (and strip cookies on redirects, OR not automatically follow redirects).

I don't need any redirects. I'm fine with redirectless approach.

So, to conclude, there's no valid reason to disallow cookies in this library. There could be a requirement of having only a single whitelisted origin if credentials: true option is passed.

At init:

if (corsAnywhere.credentials) {
  if (corsAnywhere.originWhitelist.length !== 1) {
    throw new Error('Only a single whitelisted origin is allowed when using credentials.')
  }
}

In code:

if (corsAnywhere.credentials) {
  headers['access-control-allow-origin'] = request.headers['origin']
  headers['access-control-allow-credentials'] = true
} else {
  headers['access-control-allow-origin'] = '*'
}