Is Access-Control-Allow-Origin: * insecure?
Disabling a security feature is usually a bad thing. In this case, it's fine
CORS headers
CORS headers come into play when a client makes a cross-origin request. In that case, the server must indicate that it allows the cross-origin operation otherwise the browser will reject the request. The two important points are that the target server must allow the operation and the client's browser enforces it.
This is a security feature as it protects the user by not letting random websites fetch data from sites he is logged in.
Without an Access-Control-Allow-Origin
header in the response, the browser throws an exception:
Access to XMLHttpRequest at 'https://other.example' from origin 'https://site.example'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on
the requested resource.
The Access-Control-Allow-Origin
specifies the allowed origin that can make cross-origin requests. The special value *
means "all origins" which essentially
disables this security feature. This is usually what API developers do when faced with this error. In this case, every website can send requests to the target
and read the response. And by reading, it can also forward it.
This sounds insecure, but let's see the implications!
What *
allows?
Let's say you are a user visiting a malicious third-party website at evil.example. The site has some javascript that runs a fetch to other.example/api
on your behalf. Let's also follow the example above and assume that there is a site.example which is intended to communicate with other.example/api. Since
that is a cross-origin request, other.example/api sends back an Access-Control-Allow-Origin
header.
With Access-Control-Allow-Origin: *
, evil.example is allowed to read responses from other.example/api. It seems like the malicious site can
steal information.
But in reality, it's usually not a problem. Without Access-Control-Allow-Credentials
, another CORS header, no user identifiers, such as cookies, are
sent with the request. It is like sending the request without logging in. That means a hacker has no access to information he could not gather just by
sending the request himself.
Intranet pages
But there is a scenario where it can be a problem. If only you have access to an endpoint then using the wildcard opens a vulnerability.
Still can't siphon off authenticated responses but it nonetheless opens a way to detect what kind of services are running in the local network.
If your API is not public, do not use *
.
With Access-Control-Allow-Credentials
To make an authenticated request (i.e. with cookies), set credentials: "include"
in the request:
fetch(<url>, {credentials: "include"})
In this case, the browser checks if the response contains Access-Control-Allow-Credentials: true
. But in this case *
is not allowed for
the Access-Control-Allow-Origin
.
I find it a very good compromise between security and convenience. For simple cases where disabling CORS checking means (almost) no harm, the standard allows the wildcard. But when credentials are sent it requires a stricter check.
But the Access-Control-Allow-Origin
can define only one origin. If you need to specify more than one (for example, your website is accessible under multiple
domains) then you need more effort to make it work.
When you need credentials, which is most of the time, you need to check the origin header against a whitelist and reflect that back to the user:
const allowed_origins = [
"https://site.example",
"https://www.site.example",
];
const origin = req.headers.Origin;
if (allowed_origins.includes(origin)) {
res.setHeader("Access-Control-Allow-Origin", origin);
res.setHeader("Access-Control-Allow-Credentials", "true");
res.setHeader("Vary", "Origin");
}
One important thing is to also set the Vary: Origin
header to let proxies know the response might be different for different Origin
headers.
And also don't forget that an origin contains the scheme and the port also. http://site.example is different from https://site.example and that is different from https://site.example:8443.
Conclusion
This is one of the rare cases where disabling a security feature does not harm security most of the time. The CORS specification is complicated with many
specifics but using the *
wildcard won't harm. And when you need credentials also, you'll see the error.
Just remember to not use it for non-public APIs.