41

It is said that instead of adding all domains to CORS, one should only add a set of domains. Yet it is sometimes not trivial to add a set of domains. E.g. if I want to publicly expose an API then for every domain that wants to make a call to that API I would need to be contacted to add that domain to the list of allowed domains.

I'd like to make a conscious trade off decision between security implications and less work.

The only security issues I see are DoS attacks and CSRF attacks. CSRF attacks can already be achieved with IMG elements and FORM elements. DoS attacks related to CORS can be overcome by blocking requests upon the referrer header.

Am I missing security implications?


===Edit===

  • It is assumed that the Access-Control-Allow-Credentials Header is not set
  • I know how to add a given list of domains "CORS access" and I'm therefore only interested in the security implications of adding all domains "CORS access"
brillout
  • 7,804
  • 11
  • 72
  • 84
  • you can already ping the url with img tags or iframes, CORS just lets ajax fetch the url. – dandavis Oct 11 '13 at 16:34
  • Your edit dramatically changes the implications. By not allowing any authenticated requests, this means that the endpoints you hope to expose via CORS are necessarily limited to "public" functions. Such endpoints would very likely not suffer at all from having Access-Control-Allow-Origin: *, mainly because you can't do CSRF attacks to a public endpoint. – Jake Feasel Nov 28 '13 at 21:19
  • I don't think it's even possible to use `Access-Control-Allow-Origin: *` together with `Access-Control-Allow-Credentials: true` See https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Origin and https://www.w3.org/TR/cors/#resource-requests – seanf May 04 '16 at 01:53

6 Answers6

29

Cross-Site Request Forgery attacks are far and away the primary concern that Access-Control-Allow-Origin addresses.

Ryan is certainly correct regarding content retrieval. However, on the subject of making the request there is more to say here. Many web sites now provide RESTful web services that expose a wide range of features that may involve making significant changes in the backend. Very often, these RESTful services are intended to be invoked with an XHR (e.g. AJAX) request (probably with a "Single Page Application" as the front-end). If a user has an active session granting access to these services when they visit a malicious third-party site, that site may try to invoke those REST endpoints behind the scenes, passing in values that could compromise the user or the site. Depending on how the REST services are defined, there are various ways to protect against this.

In the specific case of REST web services for a Single Page App, you can dictate that all requests to the backend REST endpoints are made with XHR and refuse any non-XHR request. You can dictate this by checking for the presence of a custom request header (something like jQuery's X-Requested-With). Only XHR-type requests can set these headers; simple GET and POST requests from forms and embedded resources cannot. Finally, the reason that we want to dictate XHR requests gets us back to the original question - XHR requests are subject to CORS rules.

If you allowed Access-Control-Allow-Origin: *, then any site could make any AJAX request on the user's behalf to your REST endpoints. If your REST endpoints involve any kind of sensitive data or allow for data persistence, then this is an unacceptable security vulnerability. Instead, enforce XHR-only requests like I described and define a whitelist of origins allowed to make those requests.

It's worth pointing out that if your REST endpoints do not expose any sensitive information, or if they don't allow the user to make any persistent data changes, then Access-Control-Allow-Origin: * may be the appropriate decision. Google Maps for instance provides read-only views into public map data; there is no reason to restrict the third party sites that may wish to invoke those services.

Bless
  • 5,052
  • 2
  • 40
  • 44
Jake Feasel
  • 16,785
  • 5
  • 53
  • 66
  • 8
    Your point assumes that the Cookies are sent along with the request which by default is not the case. For the Cookies to be sent an additional Header ``Access-Control-Allow-Credentials`` needs to be set to ``true`` – brillout Nov 27 '13 at 00:17
  • You are correct, the CSRF attack depends on that header having been set. I don't think that diminishes my point, however. If you are working with endpoints which provide sensitive or updatable data, and are attempting to do anything with CORS, then you'll have to have this header set. Once set, you'll then have to worry about which domains are requesting those authenticated endpoints, and take steps like I describe to prevent non-XHR requests from invoking them. If you aren't exposing private functionality then there is probably no reason not to simply use Access-Control-Allow-Origin: *. – Jake Feasel Nov 28 '13 at 21:23
  • 3
    You don't need any cookies if you use ``localStorage`` – brillout Nov 28 '13 at 23:33
  • 3
    I don't know what you're talking about now. If you need any kind of authenticated session for a particular user, you need some kind of persistent identifier which gets transmitted to the server. Most typically this is in the form of a cookie, it could also be done as a URL parameter or a custom header. But in any case, localStorage doesn't get sent to the server at all, and is only available to the domain that set it. So, I don't see what point you're making relative to the context of this question. – Jake Feasel Nov 29 '13 at 20:35
  • 9
    Nowdays ``localStorage`` is often used to store a session key that is sent along every request as POST/GET argument. Therefore Cookies are not needed, therefore the ``Access-Control-Allow-Credentials`` does not need to be set. As my Edit of my question specifies the ``Access-Control-Allow-Credentials`` is actually not set. Therefore no Cookies are sent. – brillout Nov 30 '13 at 22:39
  • I have my doubts about how common that actually is; you are really just re-inventing cookies, but in a more limited fashion. Storing session identifiers like you describe limits you to the domain context in which the browser is currently running. This means you get no "Single Sign On" behavior - instead, every domain that would like to use these remote endpoints will have to re-authenticate the user, rather than having the single authentication shared. In any case, we are talking about a very tangent topic now from the one you started with; I think I've covered the question you first posted. – Jake Feasel Dec 02 '13 at 05:05
  • 3
    If you don't set `Access-Control-Allow-Credentials`, and you do cookie-less authentication (ie the caller supplies a Bearer Authorization header) then you don't need to whitelist domains. A well-structured REST API can be called from any origin. – csauve Dec 08 '16 at 06:07
  • 1
    @csauve I agree with you. If your REST API is authenticated with bearer tokens instead of cookies, then this is a very reasonable approach. – Jake Feasel Jul 27 '18 at 18:40
17

Old question, but a lot of bad answers here so I have to add mine.

If you don't set Access-Control-Allow-Credentials, and you do cookie-less authentication (ie the caller supplies a Bearer Authorization header) then you don't need to whitelist origins. Just echo the origin back in Access-Control-Allow-Origin.

A well-structured REST API can be called safely from any origin.

csauve
  • 5,904
  • 9
  • 40
  • 50
  • 1
    Same POV. I.e. seems that as long as `Access-Control-Allow-Credentials` is not set then there are no security problems. That makes me wonder why the spec requires preflight when `Access-Control-Allow-Credentials` is not set. – brillout Sep 21 '17 at 11:31
  • I'm wondering the same. Why do we need preflight requests when cookies are not used? – Cristiano Coelho Jul 13 '21 at 20:08
  • It still preflights because Authorization header is not considered a "simple" header in the spec. We don't need it for our purposes, but browsers will do it anyways. https://www.w3.org/TR/2020/SPSD-cors-20200602/#simple-header Browser cannot anticipate if Access-Control-Allow-Credentials will be set until it actually makes the request. – csauve Jul 14 '21 at 21:32
11

You can send more than one, like:

Access-Control-Allow-Origin: http://my.domain.com https://my.domain.com http://my.otherdomain.com

but I would advise against it. Instead, keep a whitelist of allowed domains. Lets say:

allowed = [ "X", "Y", "A.Z" ];

Then if you get a request from X you respond with:

Access-Control-Allow-Origin: X

If you get a request from A.Z you respond with:

Access-Control-Allow-Origin: A.Z

If you get a request from a domain that is not allowed, respond with an error or no CORS policy.

All XHR requests will send an Origin header, so use that. And you only need to send the CORS policy headers for the OPTIONS request, not the GET/POST/HEAD request that follows.


The main issue I see is that you expose all your domains. Maybe you have a secure admin domain like: https://admin.mydomain.com, or maybe you have a product website that isn't ready for launch yet. You don't want to include anything that isn't absolutely necessary for the request at hand.

And * is just extremely lazy.


Halcyon
  • 57,230
  • 10
  • 89
  • 128
  • 8
    Your answer doesn't explain the security implications of allowing all domains – brillout Nov 24 '13 at 20:08
  • 1
    I think you covered it already in your question. If you want an authoritative response I would recommend you place a bounty. – Halcyon Nov 24 '13 at 23:56
  • 1
    Note that if you do this you should also include the `Vary: Origin` header to indicate that the response headers depend on the `Origin` header of the request. – augurar Apr 27 '17 at 04:40
4

CORS is about getting content back, not just making the request. When you get a resource through an img or script tag, you can trick someone's browser into making a CSRF style request. This is normal, and you can protect against that with a normal CSRF token.

With CORS enabled on all domains, you can now have javascript on an attacking site make a request and get back the content, invading their privacy.

Example:

Imagine your back enables CORS for all domains. Now I make a website that makes a request to yourimaginarybank.com/balance

An IMG request would do nothing, because my javascript can't get what was in the html of that page on your bank's website. Now that they have turned on CORS, the javascript on my site actually gets back an HTML page with your balance on it, and saves it to my server. Not only can I make a GET request like before, but now I can see what is inside. This is a huge security problem.

How to solve the problem without adding a big list into your headers? Each CORS request is made with the Origin header. The best solution is probably to read the Origin header then query a database to see if it is whitelisted as suggested by Fritz in his answer.

Ryan
  • 216
  • 1
  • 7
  • 5
    Your point assumes that the Cookies of the bank are sent along with the request which by default is not the case. For the Cookies to be sent an additional Header ``Access-Control-Allow-Credentials`` needs to be set to ``true`` – brillout Nov 27 '13 at 00:15
2

Except of csauve's one, none of the replies answer my original question.

To answer my question; It seems that as long as Access-Control-Allow-Credentials is not set then there is no security problem.

(Which makes me wonder why the spec requires preflight when Access-Control-Allow-Credentials is not set?)

brillout
  • 7,804
  • 11
  • 72
  • 84
  • IIRC it was a question of *compatibility*: the goal was to relax the Same Origin Policy, but the assumption was that servers may not be ready or able to handle non-simple requests (basically requests which could be triggered via links and forms), so this was made opt-in, and then a protocol of allowances was added. – Masklinn Aug 30 '21 at 13:28
-1

Best Practice is to first check the domain of the incoming request and then generate the response header. Depending on whether this domain is allowed to send requests, you add it (just this one) to the Access-Control-Allow-Origin response header.

Afaik, it is not even possible to add more than one domain to this header. So it's either * or one specific domain and I would always prefer not to add *

devnull69
  • 16,402
  • 8
  • 50
  • 61