Last Updated on July 15, 2024
[et_pb_section fb_built=”1″ _builder_version=”3.22″][et_pb_row _builder_version=”3.25″ background_size=”initial” background_position=”top_left” background_repeat=”repeat”][et_pb_column type=”4_4″ _builder_version=”3.25″ custom_padding=”|||” custom_padding__hover=”|||”][et_pb_text _builder_version=”3.27.4″ background_size=”initial” background_position=”top_left” background_repeat=”repeat”]
In recent web application assessments, I’ve found a number of client applications that have cross-origin resource sharing (CORS) vulnerabilities—which I flagged as Critical because they left the application wide open to a range of potentially very damaging attacks.
While CORS security issues are well described (they’re associated with vulnerability categories A5-Security misconfiguration and A8-Cross-site forgery in the OWASP Top Ten), many developers are still not aware of how to implement CORS securely, or the importance of doing so. This post offers basic guidance on how to eliminate major CORS security risk associated with mis-configurations.
What is Cross-Origin Resource Sharing?
Cross-origin resource sharing is an HTML 5 mechanism that augments and to some extent relaxes the same-origin policy to support and simplify resource sharing across domain boundaries. The CORS specification defines a set of headers that allow the server and browser to determine which requests for cross-domain resources (images, stylesheets, scripts, data, etc.) are allowed and which are not.
In a nutshell, CORS is a browser-side protection framework/standard that all browser vendors jointly support. CORS is very important in today’s world of complex, enterprise applications where a single company that has multiple applications across multiple domains that interact with each other (typically via CORS) is now the norm.
What was the problem with the same-origin policy? Basically, it was created in the early days of the web, and on its own is too restrictive for how web apps interact today.
The same-origin policy specifies that one domain cannot access resources from another domain unless both domains are “the same.” Say https://www.pps.com is requesting resources from https://www.pps.com/client. The request will be granted because the following three elements are the same for these two domains:
- The initial part of the domain name (pps.com) is the same for both
- The protocol (HTTPS) is the same for both
- Both are using the same port #443
But what happens if https://www.pps.com tries to fetch resources from, for example, https://www.pps.com/clients? Because the protocols are different, the request will be denied under the same-origin policy. In a world where one web app might be reaching out to dozens of other domains to fetch resources at runtime, a more flexible approach to securely requesting resources was needed. This led to development of CORS.
How CORS Works
It’s important to put CORS in the context of a “defense in depth” cybersecurity strategy. Web application security controls for input validation, server-side validation, output encoding, whitelist/blacklist, etc. are critically important. CORS adds another layer of security to help ensure that only trusted domains can access your site’s resources.
As mentioned above, most CORS vulnerabilities relate to poor validation practices due to response header misconfigurations. These relax security too much and allow non-trusted origins to access resources.
Here’s a simplistic analogy: You need to protect your website like you do your house. Just as you’d only give duplicate house keys to trusted family and friends—not just anyone—you likewise need to specify what origins can fetch resources from your site’s domain.
In the following example code, https://intranet.pps.com is requesting resources from https://pps.com. To trust https://intranet.pps.com and securely grant the request, you would include an Access-Control-Allow-Origin header for that specific origin:
- GET /data HTTP/1.1
Host: pps.com
…
Origin: https://intranet.pps.com - HTTP/1.1 200 OK
…
Access-Control-Allow-Origin: https://pps.com
Vulnerabilities arise when developers take shortcuts and whitelist Access-Control-Allow-Origin headers that contain wildcard characters. For example, you might write https://*.pps.com hoping to easily approve all domains that end with “pps.com.” But a hacker can exploit that by signing up for a non-secure domain like “hacked.pps.com.” This domain would be allowed to fetch resources from pps.com, because it meets the insecure criteria for a trusted domain. Using a wildcard character at the end of a domain name (e.g., https://pps.*.com) would create a similar misconfiguration/vulnerability.
One way attackers can exploit these kinds of vulnerabilities is with cross-site scripting (XSS). If your site trusts an origin with XSS vulnerabilities, an attacker could use XSS to inject some JavaScript that uses CORS to fetch sensitive resources from an otherwise secure domain. Here is an example:
- GET /api/accountNumber HTTP/1.1
Host: pps.com
Origin: https://vulnerable-third-party.com
Cookie: sessionid=… - HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://vulnerable-third-party.com - https://vulnerable-third-party.com/?xss=<script>CORS-stuff-here</script>
CORS misconfigurations can also give attackers access to internal sites behind the firewall using cross-communication types of attacks. Such attacks can succeed because developers disable CORS security for internal sites because they mistakenly believe these to be safe from external attacks.
A typical vulnerability scenario involves setting Access-Control-Allow-Origin to *, plus setting the Access-Control-Allow-Credentials response header to TRUE. Together, these two response headers tell the app to trust resource requests from all origins, without requiring credentials.
Such an attack generally requires a user to have a CORS-vulnerable intranet site open in one browser tab, while accessing a malicious external site in another tab (such as in response to a phishing request). From there, the malicious domain can use the browser as a proxy to make an illicit resource request of the vulnerable intranet domain in the other tab. Since any origin is allowed and no credentials are needed, the request will be honored.
Note that if Access-Control-Allow-Origin is * but Access-Control-Allow-Credentials is not TRUE, the hacker can only access unauthenticated content.
This script illustrates how Access-Control-Allow-Origin can be misconfigured to allow untrusted access to internal sites:
- GET /reader?url=doc1.pdf
Host: intranet.pps.com
Origin: https://vulnerable-third-party.com - HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
A third type of CORS attack involves intercepting traffic over deprecated TLS protocols or in the clear. This can happen if an app that uses HTTPS whitelists a domain that is using HTTP, with Access-Control-Allow-Origin = * and Access-Control-Allow-Credentials = TRUE.
For example, https://pps.com may only accept TLS 1.2 per current best practices. But if Access-Control-Allow-Origin is set to *, a misconfigured site like https://vulnerable-third-party.com that is communicating in plain text can request resources from https://pps.com. An attacker can send a resource request to https://vulnerable-third-party.com, which will redirect it to https://pps.com. Since the attacker can intercept/spoof the request, they can read the response and likely obtain the session token.
This example illustrates a misconfiguration that is vulnerable to a TLS attack:
- GET /api/AccountDetails HTTP/1.1
Host: pps.com
Origin: https://vulnerable-third-party.com
Cookie: sessionid=… - HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://vulnerable-third-party.com
Access-Control-Allow-Credentials: true
How to Avoid CORS Security Vulnerabilities
To implement CORS securely, you need to associate a validation list (whitelist) with Access-Control-Allow-Origin that identifies which specific domains (e.g., your company’s other domains) can access resources. Then your application can validate against this list when a domain requests access. You also don’t want to define your Access-Control-Allow-Origin header as NULL, as an attacker can send a request with a NULL origin that would bypass other controls.
Similarly, with Access-Control-Allow-Methods you should specify exactly what methods are valid for approved domains to use. Some may only need to view resources, while others need to read and update them, and so on.
It is quite easy for a hacker to setup a traffic viewer and observe what requests are passing back and forth from your site and what the responses are. From this, they can determine whether your site is vulnerable to a CORS-based attack.
Therefore, you should be validating each and every domain that is requesting your site’s resources, as well as the methods other domains can use if their requests for access are granted. You can easily identify CORS security vulnerabilities by reviewing the above headers in the application’s response and validating the values of those headers. Using open source scanners is also a great way to discover CORS security vulnerabilities.
Next Steps
Validating origins and methods is just the beginning of robust, flexible CORS security. For example, you could additionally require credentials from requestors by setting up the header Access-Control-Allow-Credentials.
As more and more web applications rely on cross-domain resource exchange, and more and more programming language frameworks (e.g., Java, Spring, RESTful services) support CORS in various ways, it’s essential—at a minimum—that you implement CORS as described above to help prevent data loss, data exfiltration and/or data availability concerns.
To identify security vulnerabilities in critical applications that put your data and operations at risk, including how best to prioritize and mitigate them, contact Pivot Point Security.
For more information:
- OWASP guidance on testing your CORS mechanism
- OWASP guidance on using the Origin header in CORS
- A discussion of the security impact of a misconfigured CORS implementation (with code examples)