Same-origin policy is a security enforcement found in most common browsers that restricts the way a document or script (or other data) that gets loaded from one origin can communicate and associate with properties of another origin. It's a crucial concept of security which runs web applications of various kinds.
To understand the same-origin policy better, let us consider an example. Imagine that you're logged into your webmail, such as Gmail, in one browser tab. You open a page in another browser tab that has some pieces of JavaScript (JS) that attempts to read your Gmail messages. This is when the same-origin policy kicks in: as soon as an attempt is made to access Gmail from some other domain that is not Gmail then the same-origin policy will prevent this interaction from happening. So, basically, the same-origin policy prevented a random web page which was not a part of Gmail from performing actions on your behalf on an actual Gmail web page.
Allow me to explain more specifically what origin actually means. Origin is considered on the basis of protocol, port number, and, more importantly, the hostname of the webpage. Please note that the path of the page does not matter as long as the rest of the mentioned things are satisfied.
Keep in mind that the same-origin policy is not only for JS but for cookies, AJAX, Flash, and so on. Data stored inside localStorage
is also governed by this policy, that is, origin-separated.
The following table exhibits different same-origin policy results based on hostname, port number, and protocol when compared with the origin: http://example.com/meme/derp.html
.
Demonstration of the same-origin policy in Google Chrome
Now we've geared up with the basics of the same-origin policy, let me try to demonstrate an example in which I'll try to violate the same-origin policy and trigger the security mechanism:
As soon as this code runs inside the Chrome browser, it throws the following message in the console.log()
output:
I ran the script from output.jsbin.com
and Chrome's same-origin policy effectively kicked in and prevented output.jsbin.com
from accessing the contents of the example.com
iframe.
JS provides a way to change origins if certain conditions are met. The document.domain
property allows the origin of the current page to change into a different origin, for example origin A can switch to origin B; this will only work if the current page is the subset of the main domain.
Let me explain the mentioned concept with an example. Consider a page running under example.com
, which has two iframes, abc.example.com
and xyz.example.com
. If either of these iframes issues document.domain = 'example.com'
then further same origin checks will be based on example.com
. However, as I mentioned, a page can't misuse this functionality to impersonate a completely different domain. So, malicious.com
cannot issue an origin to change to bankofamerica.com
and access the data of it:
This screenshot shows the error thrown by the Google Chrome browser when example.com
attempts to impersonate bankofamerica.com
by changing its document.domain
property.
Quirks with Internet Explorer
As expected, Microsoft Internet Explorer (IE) has its own exceptions to the same-origin policy; it skips the policy checks if the following situations are encountered:
- IE skips the origin check if the origin falls under the Trust Zone, for example, internal corporate websites.
- IE doesn't give any importance to port numbers, so
http://example.com:8081
and http://example.com:8000
will be considered as the same origin; however, this is won't be true for other browsers. For example, there are browser bugs which can lead to SOP bypass; one such example is an SOP bypass in Firefox abusing the PDF reader – https://www.mozilla.org/en-US/security/advisories/mfsa2015-78/.
Sometimes, there exists a need to communicate across different origins. For a long time, exchanging messages between different domains was restricted by the same-origin policy. Cross-domain messaging (CDM) was introduced with HTML5; it provides the postMessage()
method, which allows sending messages or data across different origins.
Suppose there is an origin A on www.example.com
which, using postMessage()
, can pass messages to origin B at www.prakharprasad.com
.
The postMessage()
method accepts two parameters:
message
: This is the data that has to be passed to the receiving windowtargetDomain
: The URL of the receiving window
Sending a postMessage():
Receiving a postMessage():
AJAX and the same-origin policy
As of today, all interactive web applications make use of AJAX, which is a powerful technique that allows the browser to silently exchange data with the server without reloading the page. A very common example of AJAX in use is different online chat applications or functionality, such as Facebook Chat or Google Hangouts.
AJAX works using the XMLHTTPRequest()
method of JS. This allows a URL to be loaded without issuing a page refresh, as mentioned. This works pretty decently till the same-origin policy is encountered, but fetching or sending data to a server or URL which is at a different origin is a different story altogether. Let us attempt to load the home page of packtpub.com
using a web page located at output.jsbin.com
through an XMLHTTPRequest()
call. We'll use the following code:
As soon as this code runs, we get the following security error inside the Google Chrome browser:
This error looks interesting as it mentions the 'Access-Control-Allow-Origin' header and tells us that packtpub.com
effectively lacks this header, hence the cross-domain XMLHTTPRequest()
will drop as per security enforcement. Consider an example in which a web page running at origin A sends an HTTP request to origin B impersonating the user and loads up the page, which may include Cross-Site Request Forgery (CSRF) tokens, and then they can be used to mount a CSRF attack.
So the same-origin policy basically makes calling separate origin documents through AJAX functions a problem. However, in the next section, we'll attempt to dig deeper into this.