Published on
Modern web browsers provide many built-in security mechanisms to defend against attackers. Same-origin policy, Cookie Policy, Content Security Policy, browser sandbox, and XSS protection are just a few. While these protections cannot fully protect us from all types of attacks, they provide a minimum layer of security that makes the attacker's work more difficult. In this article, we will focus on same-origin policy and Cross-Origin Resource Sharing (CORS).
What Does "Origin" Mean?
The term of origin in this context denotes the exact location of a specific resource (image, script, etc.). It consists of three main elements: the protocol (e.g., HTTP or HTTPS), the hostname (e.g., hackedu.io) and the port (80, 443, 8080, etc.).
When the browser performs same-origin policy (SOP) checks, it compares the originating location with the location of the requested resource. If they perfectly match, then the origin is the same and the browser allows the requested resource to be loaded. For example, http://www.hackedu.io/about is the same origin as http://www.hackedu.io/contact in contrast to http://hackedu.io/about, which has a different origin (different hostname).
Same-Origin Policy
The same-origin policy is an important security feature of any modern browser. Its purpose is to restrict cross-origin interactions between documents, scripts, or media files from one origin to a web page with a different origin.
The HTTP protocol was extremely simple when it was first created. At that time websites consisted of static, non-interactive pages. There were no animations or menus, and definitely no eye-catching designs or interactive pages.
As the Web started to grow in popularity it had to keep up with the users' needs. So new versions of the HTTP protocol were introduced that supported new technologies. Two of the most important early improvements were the addition of the JavaScript language and the Document Object Model (DOM) API. These features made it possible to create interactive web pages that update content in real time based on how the user interacts with the page. A page can simultaneously load documents, images, or scripts from multiple different sites to achieve its purpose.
Why The Same-Origin Policy Was Necessary?
Let's suppose someone manages to trick you into visiting https://faceboook.com (extra "o"). On this website, there is an iframe which loads the content of the correct site https://facebook.com. As usual, you login into your Facebook account. Now, the malicious website (faceboook.com) can read your private messages or perform actions on your behalf with just a few Asynchronous JavaScript and XML (AJAX) requests. One such attack is known as Cross-Site Request Forgery (CSRF).
The flexibility offered by Javascript and the DOM API introduced these security concerns. To address this problem, Netscape introduced the same-origin policy to prevent cross-origin resources from being accessed. Consequently, this protection mechanism quickly became a necessity for ensuring the privacy of data in modern browsers.
What Does The Same-Origin Policy Allow?
It is a common misconception that same-origin policy blocks all cross-origin resources. If that were true Content Delivery Networks (CDNs) wouldn't exist. There are several HTML tags that generally allow embedded cross-origin resources: iframe, img, script, video, link, object, embed, form. Please note that they do not also permit cross-origin read. The difference between embedding and reading a resource is that when embedded, the resource is copied from the external origin and rendered locally, while reading the resource means their origin is preserved.
Although same-origin policy is an important browser security feature that provides significant protection against malicious scripts, it is far from perfect. In some cases it is not restrictive enough and common web vulnerabilities such as Cross-Site Request Forgery (CSRF) arise. In other cases the policy is too restrictive and tangles the workflow of the web application. Engineers introduced a standard called Cross-Origin Resource Sharing as a way to relax the same-origin policy's restrictions.
What Is CORS?
Cross-Origin Resource Sharing (CORS) allows servers to specify trusted origins that can be used in cross-origin requests. A CORS request can be either Simple or Preflight.
If the HTTP method is GET
, POST
, or HEAD
and the Content-Type is text/plain
, application/x-www-form-urlencoded
or multipart/form-data
, then it is a Simple request and can be initiated without any preliminary checks. In this case, the browser adds an Origin: header describing the origin from where the request has been initiated. Once the request is received the server tells the browser if the CORS request is valid by appending the Access-Control-Allow-Origin
header to the response. Here is an example to make things clear.
Let's suppose that you navigate to https://www.hackedu.io and your browser makes a request in the background to an API to get all available courses. It should look like this:
GET /courses HTTP/1.1
Host: api.hackedu.io
Origin: https://www.hackedu.io
...
And the server responds with:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://www.hackedu.io
Content-Type: application/json
...
Once the browser receives the HTTP response, it checks whether the request origin matches the value of Access-Control-Allow-Origin header. If the check fails, the response is blocked immediately. Also, one important note is that the Access-Control-Allow-Origin header supports only a single origin. This means you cannot specify multiple websites as the value of this header (e.g., Access-Control-Allow-Origin: https://www.hackedu.io, http://example.com). However, it supports the wildcard operator (*
) which tells the server that any cross-request should be allowed. This is a bad idea unless you want anyone to consume your restful API.
Any request that is not considered Simple (i.e, uses a different HTTP method than GET
, POST
, or HEAD
, or the Content-Type is not one of those mentioned above) is called a Preflight request. This is because the browser sends a preflight request before the original request to make sure that the original request is acceptable to the server. Below is an example:
Let's suppose your browser wants to send a DELETE request to https://api.hackedu.io/account/me. Since the HTTP method is not GET
, POST
, or HEAD
, the browser initiates a preflight request that looks like this:
OPTIONS /account/me HTTP/1.1
Origin: https://hackedu.io
Access-Control-Request-Method: DELETE
Once the server receives the preflight request it responds with headers which tell if the preflight request has been accepted:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://hackedu.io
Access-Control-Allow-Methods: GET,DELETE,HEAD,OPTIONS
Now, the original DELETE
request can be forwarded to the server. Also, it will contain the Origin header similar to the Simple requests.
CORS is just one method to relax same-origin policies. There are also other methods, such as JSON with Padding (JSONP) or cross-document messaging. However, the problem with JSONP is that it is read-only and became incapable of serving the requirements imposed by complex web applications. While CORS has its own downsides (e.g. you cannot specify multiple whitelisted domains), it is still a better choice than the other alternatives mentioned and it will most likely be improved in the future.
If you found this post helpful, you may also be interested in learning more about how to correctly prevent SQL Injections.