Cross-Origin Resource Sharing (CORS) is a mechanism which uses HTTP headers to tell a browser that a web application running at one origin has permission to access selected resources from a server at a different origin. This functionality exists for cases where an application developer would want to deliberately ignore a same origin policy (SOP) which mitigates many common attacks against browsers (notably cross-site scripting). This makes it a powerful tool for applications deployed on our increasingly distributed Internet but also a potential source of vulnerability.
Background: What is the Same-Origin Policy?
The Same-Origin Policy is a mitigating control which restricts how scripts or other resources from one origin interact with resources from a third party.
Some examples of cross-origin requests are:
- A different domain (example.com to test.com)
- A different subdomain (example.com to test.example.com)
- A different port (example.com to example.com:8080)
- A different protocol (https://example.com to http://example.com)
The Same-Origin policy mitigates some common web application attacks and is a critical tool for protecting users and applications on the modern web.
More details are available at the Mozilla Developer Network.
How Does CORS Work?
There are two headers which primarily govern CORS: Access-Control-Allow-Origin, and Access-Control-Allow-Credentials. When a script from foo.com wants to make a request to bar.com, the browser sends a pre-flight request to bar.com with foo.com in the Origin header. This is how the browser asks for permission for the resource to interact with the requesting site. The service on bar.com will then return an Access-Control-Allow-Origin response header if the domain matches an allowed origin. Only if the domain matches the one hosting the script does the browser send the HTTP request.
The Access Control Allow Origin header accepts various origins:
- Domains and subdomains (http://blah.example.com, https://example.com)
- Wildcards (*)
- ‘Null’
The case of the ‘Null’ origin is interesting and can result in some misconfigurations which we do not explore in this article. ‘Null’ is an origin assigned by default to many documents, sandboxed code, and redirects. While this is useful, it can potentially open up your application to attack. The Mozilla Developer Network and w3c specify that the null origin should not be used. More details about that vulnerability are available in Portswigger’s blog on the subject.
Access-Control-Allow-Credentials is a boolean – that is, it can be only True or False. If our application requires a user to be authenticated to use it, and that application makes CORS requests on behalf of that logged in user, we require Access-Control-Allow-Credentials to be true. If the resources we are accessing do not absolutely require that the user’s credentials be passed to the cross-domain resource, we should set this to False.
In some cases, you may want to bypass the same-origin policy to share content from a CDN, an S3 bucket, or an on-premises server.
- If you host your website in S3, but want to use JavaScript to be able to make authenticated GET and PUT requests against the same bucket by using the Amazon S3 API endpoint for the bucket.
- Loading a web font.
- Loading dynamic content from another webpage.
- Making an authenticated GET request to an on-prem server.
If you want to allow any domain to make a cross-origin request, you can certainly use the setting Access-Control-Allow-Origin: *
Unfortunately, per the CORS specification – if you have wildcards in your ACAO header, then Access-Control-Allow-Credentials cannot be true. While you might think that maintaining a list of allowed origins makes good sense, it turns out that CORS policy cannot accept a list either! So in order to allow credentials to be passed over a CORS request, you must process the origin and ensure that it’s valid. As we’ll explain, this is a recipe for disaster when developers are not careful.
CORS in the Cloud
In addition to the fact that any VM you deploy in your IaaS environment which supports HTTP can enable CORS, many individual services support CORS, including:
- Azure Functions
- Azure Logic Apps
- Azure Blob Storage
- Google Cloud Functions
- Google Cloud Storage
- Google Cloud Endpoints
- Amazon Lambda
- Amazon S3
- Amazon API Gateway
Clearly, this is a functionality that is widespread, and often used by web developers. As your infrastructure grows and matures, CORS is very likely to see use across your cloud infrastructure.
In the case of Lambda and API Gateway, Amazon provides the following guidance:
“To enable cross-origin resource sharing (CORS) for a proxy integration, you must add Access-Control-Allow-Origin: <domain_name> to the output headers. <domain_name> can be * for any domain name.”
As mentioned above, this works wonderfully, as long as you don’t need the Access-Controls-Allow-Credentials field to be true. If you need credentials, you’ll need to process the user-supplied origin. If you have never deployed CORS before and turn to the Internet for a template, you may end up using one of the many code examples which only reflect the origin and do not provide specific in