CORS Vulnerabilities and Defenses
Understanding Same-Origin Policy
The Web's Neighborhood Watch
Imagine your web browser is like a big city. Each website you visit is a separate building. The Same-Origin Policy (SOP) is like a fundamental rule of this city: the residents of one building generally can't look inside another building or interact with its inhabitants. It’s a core security feature built into every modern browser.
The Same-Origin Policy prevents a script loaded from one origin from getting or setting properties of a document from a different origin.
But what exactly is an “origin”? It's not just the website's domain name. An origin is defined by the combination of three things: the protocol (like http or https), the hostname (like www.example.com), and the port number (like 80 or 443).
If any one of these three parts is different between two URLs, the browser considers them to be from different origins.
| URL | Compared to http://www.example.com/dir/page.html | Outcome |
|---|---|---|
http://www.example.com/dir2/other.html | Same protocol, host, and port | Same Origin |
https://www.example.com/dir/page.html | Different protocol (https) | Different Origin |
http://en.example.com/dir/page.html | Different host (en.example.com) | Different Origin |
http://www.example.com:81/dir/page.html | Different port (81) | Different Origin |
This policy is crucial for your safety. Without it, if you were logged into your online bank in one browser tab and visited a malicious website in another, that malicious site could potentially run a script to read your account balance or even initiate a transfer. SOP is the security guard that stops this from happening.
SOP in Action
Let's walk through a practical example. You're logged into https://mybank.com. In another tab, you accidentally click a link to https://evil-site.com. This evil site has JavaScript code designed to fetch information from mybank.com.
// This script runs on https://evil-site.com
fetch('https://mybank.com/account-details')
.then(response => response.json())
.then(data => {
// Send the stolen data to the attacker's server
console.log('I have your data!', data);
})
.catch(error => {
// This is what actually happens
console.error('Cross-Origin Request Blocked:', error);
});
When the script on evil-site.com tries to make a request to mybank.com, the browser steps in. It checks the origin of the script (https://evil-site.com) and the origin of the requested resource (https://mybank.com). Since they don't match, the browser blocks the request. The malicious script receives an error, and your banking information stays safe. The browser effectively builds a wall between the two origins.
The Limits of Isolation
While SOP is a fantastic security tool, its strictness can also be a limitation. The modern web is built on interconnectivity. A single web page often needs to pull in resources from many different origins. Think about a news website that embeds a video from a video-sharing platform, uses a font from a font service, and pulls weather data from a third-party API.
Each of these is a cross-origin request. SOP by default would block the scripts on the news site from interacting with the data from these other origins. This makes building complex, feature-rich applications very difficult.
If every website were a completely isolated island, the modern, collaborative web as we know it couldn't exist.
This restriction created a need for a mechanism that would allow servers to selectively relax the Same-Origin Policy. Developers needed a way to tell the browser, "Hey, it's okay for that specific origin to access my data, but no one else." This need for controlled, safe cross-origin communication is what led to the development of Cross-Origin Resource Sharing (CORS).