159

I can't really fully understand what same origin domain means. I know it means that when getting a resource from another domain (say a JS file) it will run from the context of the domain that serves it (like Google Analytics code), which means it can't modify the data or read the data on the domain that "includes the resource".

So if domain a.com is embedding a js file from google.com in its source, that js will run from google.com and it can't access the DOM\cookies\any other element on a.com -- am I right?

Here is a definition for the same origin policy which I can't really understand:

The same-origin policy is a key mechanism implemented within browsers that is designed to keep content that came from different origins from interfering with each other. Basically, content received from one website is allowed to read and modify other content received from the same site but is not allowed to access content received from other sites.

What does that really mean? Can you please give me a real life example?

Another question is: what is the purpose of Origin header and how do cross domain requests still exist? Why doesn't it influence the security or the same origin policy?

YSY
  • 2,229
  • 4
  • 20
  • 16
  • 1
    Similar on SO: http://stackoverflow.com/questions/1830050/why-same-origin-policy-for-xmlhttprequest , requesting specific simple example: http://stackoverflow.com/questions/14667189/simple-example-for-why-same-origin-policy-is-needed?rq=1 – Ciro Santilli OurBigBook.com Nov 08 '14 at 16:14

4 Answers4

133

Why is the same origin policy important?

Assume you are logged into Facebook and visit a malicious website in another browser tab. Without the same origin policy JavaScript on that website could do anything to your Facebook account that you are allowed to do. For example read private messages, post status updates, analyse the HTML DOM-tree after you entered your password before submitting the form.

But of course Facebook wants to use JavaScript to enhance the user experience. So it is important that the browser can detect that this JavaScript is trusted to access Facebook resources. That's where the same origin policy comes into play: If the JavaScript is included from a HTML page on facebook.com, it may access facebook.com resources.

Now replace Facebook with your online banking website, and it will be obvious that this is an issue.

What is the origin?

I can't really fully understand what same origin domain means. I know it means that when getting a resource from another domain (say a JS file) it will run from the context of the domain that serves it (like google analytics code), which means it can't modify the data or read the data on the domain that "includes the resource".

This is not correct: The origin of a JavaScript file is defined by the domain of the HTML page which includes it. So if you include the Google Analytics code with a <script>-tag, it can do anything to your website but does not have same origin permissions on the Google website.

How does cross domain communication work?

The same origin policy is not enforced for all requests. Among others the <script>- and <img>-tags may fetch resources from any domain. Posting forms and linking to other domains is possible, too. Frames and iframes way display information from other domains but interaction with that content is limited.

There are some approaches to allow XMLHttpRequest (ajax) calls to other domains in a secure way, but they are not well supported by common browsers. The common way to enable communication with another domain is JSONP:

It is based on a <script>-tag. The information, which shall be sent to another domain, is encoded in the URL as parameters. The returned JavaScript consists of a function call with the requested information as parameter:

<script type="text/javascript" src="http://example.com/
     ?some-variable=some-data&jsonp=parseResponse">
</script>

The dynamically generated JavaScript from example.com may look like:

parseResponse({"variable": "value", "variable2": "value2"})

What is Cross Site Scripting?

Cross Site Scripting is a vulnerability that allows an attacker to inject JavaScript code into a website, so that it originates from the attacked website from the browser point of view.

This can happen if user input is not sufficiently sanitised. For example a search function may display the string "Your search results for [userinput]". If [userinput] is not escaped an attacker may search for:

<script>alert(document.cookie)</script>

The browser has no way to detect that this code was not provided by the website owner, so it will execute it. Nowadays cross site scripting is a major issue, so there is work done to prevent this vulnerability. Most notable is the Content Security Policy approach.

Hendrik Brummermann
  • 27,118
  • 6
  • 79
  • 121
  • This was incredibly helpful to me. Can you elaborate on the iFrame case? Can a parent access child variables and vice versa? (sameorigin, ignoring sandbox and restricted configurations) – makerofthings7 Mar 29 '13 at 16:12
  • In my opinion Same Origin Policy is very useless. It only gave me trouble when I want to do cross domain GET requests, and it can't even block many XSS attacks because there are so many other ways to get around it. – Derek 朕會功夫 Nov 25 '13 at 16:33
  • 13
    Why didn't browser vendors choose this solution: if a.com makes a request to a resource from facebook.com, that request is made without any cookies, that way facebook.com will treat it as unauthenticated. Doesn't that close the vulnerability without banning cross-origin requests? – Flimm Jun 24 '15 at 09:19
  • I am trying to understand it as well: is it really the cookie that makes same origin required? It seems that implicit cookie sending is what creates all the trouble and without it, there should be no danger in allowing CORS by default? – Ilya Chernomordik Feb 01 '16 at 15:50
  • 4
    @Flimm, you just described the intended behaviour for browsers implementing a new SameSite cookie flag. A site can benefit from other sites sharing read access when the former embeds other content into own pages, and Same Origin Policy allows that. The policy will prevent from changing the ownership of the embedded pieces (which would otherwise enable malicious sites stealing embedded bank pages etc.) SOP allows "simple" (those with historic content type bodies, without authentication or extra headers) POST actions across domains. I guess this is good for pingbacks, and bad for CSRF vulns. – eel ghEEz Jul 08 '16 at 19:55
  • @Flimm I'm pretty sure browsers do that these days which does seem to undermine the use case for sop. – Isaac Kleinman Jul 12 '19 at 16:43
  • 1
    @Flimm See https://security.stackexchange.com/questions/221658/what-would-happen-if-some-random-webpage-made-an-ajax-request-for-http-127-0-0 for an example of an attack that does not involve cookies, which SOP prevents. – mti2935 Jan 22 '21 at 22:46
51

What does that really mean? Can you please give me a real life example?

Attack example 1: Cross-Site Request Forgery (CSRF) with an HTML form

On page at evil.com the attacker has put:

<form method="post" action="http://bank.com/trasfer">
    <input type="hidden" name="to" value="ciro">
    <input type="hidden" name="ammount" value="100000000">
    <input type="submit" value="CLICK TO CLAIM YOUR PRIZE!!!">
</form>

Without further security measures:

  • the request does get sent. The SOP does not forbid this request from being sent.
  • it includes authentication cookies from bank.com which log you in. As mentioned at "Are all cross-origin requests forbidden?" below, sending cookies across domains allows you for example to click a <a link to bank.com and go to that website already logged-in, which is what users generally want.

It is the synchronizer token pattern, alone, even without the SOP, prevents this from working.

Synchronizer token pattern

For every form on bank.com, the developers generate a one time random sequence as a hidden parameter, and only accept the request if the server gets the parameter.

E.g., Rails' HTML helpers automatically add an authenticity_token parameter to the HTML, so the legitimate form would look like:

<form action="http://bank.com/transfer" method="post">
  <p><input type="hidden" name="authenticity_token"
            value="j/DcoJ2VZvr7vdf8CHKsvjdlDbmiizaOb5B8DMALg6s=" ></p>
  <p><input type="hidden" name="to"      value="ciro"></p>
  <p><input type="hidden" name="ammount" value="100000000"></p>
  <p><button type="submit">Send 100000000$ to Ciro.</button></p>
</form>

as mentioned at: https://stackoverflow.com/questions/941594/understanding-the-rails-authenticity-token/26895980#26895980

So if evil.com makes a post single request, they would never guess that token, and the server would reject the transaction!

See also: synchronizer token pattern at OWASP.

Attack example 2: Cross-Site Request Forgery (CSRF) with JavaScript AJAX

But then, what prevents the evil.com from making 2 requests with JavaScript, just like a legitimate browser would do:

  1. XHR GET for the token
  2. XHR POST containing the good token

so evil.com would try something like this (jQuery because lazy):

$.ajax({
  url: 'http://bank.com/transfer',
  type: "GET",
  xhrFields: {withCredentials: true},
});
// Parse HTML reply and extract token.
$.ajax({
  url: 'http://bank.com/transfer',
  type: "POST",
  data: {
    to: 'ciro',
    ammount: '100000000',
    authenticity_token: extracted_token
  },
  xhrFields: {withCredentials: true},
});

Here the attacker used withCredentials: true because XHR does not send cross request cookies without it, see also: Why are cookies sent with HTML page's cross domain requests but not with JS's XHR?

This is where the SOP comes into play. Although the GET and POST do actually send the authenticated request just like the HTML form, the sender's browser prevents the JavaScript code from reading the HTML reply back, because the request was sent to a separate domain!

The Chromium developer console shows an error for it of type:

Access to XMLHttpRequest at 'http://bank.com' from origin 'http://evil.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

which has been asked at: https://stackoverflow.com/questions/20035101/why-does-my-javascript-code-get-a-no-access-control-allow-origin-header-is-pr

Why not just not send cross request cookies instead?

What if implementations had a rule like: "allow any request, but only send cookies on current domain XHR"? Wouldn't that be simpler?

But that would still allow for another type of attack: when authentication is based not on cookies, but on source (IP) of the request!

For example, you are in your company's intranet and from there you can access an internal server, which is not visible from the outside and serves secret data.

Are all cross-origin requests forbidden?

Even forgetting CORS, no, we do them every day!

The only thing that can't ever be allowed is reading the result of such request back in JavaScript, as that would beat the synchronizer token pattern.

From MDN:

  • Cross-origin writes are typically allowed: links, redirects and form submissions.

    [My comment]: e.g., when you click a link, you often expected to go logged in to the website, and that requires making an authenticated GET request that returns the new page.

  • Cross-origin embedding is typically allowed: images, external CSS and Javascript, iframes.

  • Cross-origin reads are typically not allowed: XHR (example above), iframe read.

    However, read access is often leaked by embedding. For example you can read the width and height of an embedded image, the actions of an embedded script, or the availability of an embedded resource (and thus possibly if the user is logged in or not on a given domain)

Other prevention approaches

Other prevention approaches: JWT

JSON Web Token is quite a popular alternative to cookies + synchronizer token pattern circa 2020.

What this method does is:

  • store a signed token in window.localStorage
  • whenever you want to make an authenticated request to the server, send a header Authentication: <token>. Note that this can only be done from JavaScript.

This method works because unlike cookies, localStorage is only available when you make requests from the website itself (through JavaScript), thus dispensing the synchronizer token.

Then, when users first visit the website, they are initially logged off, and a dummy loading page shows.

Then the browser runs the JavaScript is just received from the server, reads localStorage (now that we are on the correct domain already) and sends an authenticated GET request to an API path to get only the data without HTML, usually as JSON.

Finally the JavaScript renders that data on the browser.

This approach has become particularly popular due to the popularity of Single Page Applications, where the simplest implementation approach is this two-step get dummy page then populate it with the API data.

So this basically carries the tradeoffs:

  • advantages:
    • simpler to implement since no synchronizer on every form
    • the usual SPA advantages: you get only data after the initial request, not HTML tags
  • disadvantages:
    • the usual SPA disadvantages:
      • during first load the user might see annoying loading dummy page elements
      • the website is not visible without JavaScript

See also

  • @Flimm The SOP does solve it, and that is why I posted it to show how the SOP is useful :-) The SOP blocks both GET and POST. I think in theory it would be possible to have a scheme where only GET is blocked. But that would be likely a bad idea as it relies on assumptions that are not always true, e.g. that a POST request does not return a form with the token (true, it usually redirects: http://stackoverflow.com/questions/420103). – Ciro Santilli OurBigBook.com Jun 24 '15 at 09:48
  • @Flimm because if there was no token then I could use a form that makes a POST request from `evil.com` to the bank. That is not forbidden by the SOP, like many other important cross requests like hyperlink GET. The SOP only prevents reading data back. – Ciro Santilli OurBigBook.com Jun 24 '15 at 14:46
  • @Flimm I am not an expert, but that's what I understood. Let me know if you find an error ;-) And as usual, try it out: upload a test attack on your github pages website for example makes a post request to say, a personal GitLab repo (which uses Rails and thus token) creating an issue. Then observe traffic with wireshark. The POST request should go, but have no effect. – Ciro Santilli OurBigBook.com Jun 24 '15 at 14:54
  • @Flimm rejected the edit because the request is never made by the browser. Also added a small explanation about the POST form we've talked about. – Ciro Santilli OurBigBook.com Jun 24 '15 at 15:06
  • @Flimm I think the SOP makes the browser not send requests that *would be able to* get the response back, it does not block the response. – Ciro Santilli OurBigBook.com Jun 24 '15 at 15:07
  • 1
    @Flimm thanks for the edit! I have modified it slightly to make more direct, but it definitely makes things clearer. – Ciro Santilli OurBigBook.com Jun 24 '15 at 15:46
  • 1
    The first example is great, Ciro. I have a question about this statement: "Without further security measures, this would work because authentication cookies from bank.com would be sent and authenticate you." ... I'm not familiar with authentication cookies. Are the authentication cookies sent to bank.com in this situation because the user had logged in to bank.com previously, and at that time, the browser stored them? – Niko Bellic Dec 14 '15 at 20:49
  • 1
    @NikoBellic yup :-) That's how most websites save your login. – Ciro Santilli OurBigBook.com Dec 14 '15 at 20:52
  • "amount" not "ammount" – Federico Jul 02 '17 at 16:08
  • @Federico the bank web devs were non native :-) – Ciro Santilli OurBigBook.com Jul 02 '17 at 18:26
  • @CiroSantilli包子露宪六四事件法轮功 In attack example 1, SOP does not solve it, request can still be made, it only stop the access of page javascript to read the data send in response. Suppose mybank.com/transferto=x&amount=y, transfers y amount of money to x, when someone is logged in. It will still work, as SOP only ensures that you cannot read the response – Suraj Jain May 28 '18 at 12:50
  • https://security.stackexchange.com/a/81222/166709 – Suraj Jain May 28 '18 at 12:57
  • "But because of the SOP, the browser prevents this request from being made.", the request can be made, but it's just the brower does not allow the site to read the response content. I used flask to run a simple test on server. The server did receive the request. – Rick Jul 29 '19 at 13:51
  • @Rick thanks for mentioning this. What is the observed JavaScript error message / behavior when you try to read? I'll fix this later on. – Ciro Santilli OurBigBook.com Jul 29 '19 at 13:58
  • 1
    Chrome dev tool console tab: `Access to XMLHttpRequest at 'http://150.109.107.xxx/chat.txt' from origin 'http://localhost:8000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.` But I can still see the response in the network tab, I think it's just the brower prevent the site to read it. The user can still read the response. – Rick Jul 29 '19 at 15:05
  • @Rick thanks enormously, I have corrected the answer. – Ciro Santilli OurBigBook.com Aug 02 '19 at 07:50
20

Don't worry. I found it tricky to wrap my head around too.

It turns out that Google Analytics can, in theory, do anything they want to your users. (<script> tags create an exception to same-origin policy restrictions)

That's one of the biggest reasons XSS attacks are a Bad Thing™ and one reason Mozilla designed Content Security Policy which is now on its way to being standardized. (WebKit's implementation of the HTML5 iframe sandbox attribute can also be helpful)

Because they couldn't get away with breaking backward compatibility, the same-origin policy is more about preventing things like <iframe> and XMLHttpRequest from tricks like using your "Remember Me" cookies or lying login dialogs to access your accounts. (The X-Frame-Options header allows you to lock frames down even further to protect against things like Clickjacking.)

The Origin header is used by a mechanism named "Cross-Origin Resource Sharing" which allows sites to grant limited exceptions to same-origin policy for safe cross-site interaction. (supported fully in all current browsers except Opera and Internet Explorer and partially in IE8+ using the proprietary XDomainRequest object which omits cookies)

Basically, when you try to make an XMLHttpRequest to a different domain, the browser will do one of two things:

  1. If it's a GET or POST request which meets certain limits (which the makers of the standard have determined to add no extra risk for CSRF attacks) then the browser just passes the request through.

  2. Otherwise, it does what's called a "preflighted request", where it first sends an OPTIONS request instead and only does what you requested if the checks pass for the OPTIONS request.

In either case, the browser appends an Origin header which tells the target site who's calling. (It's sort of like the Referer header, but required and more strictly specified to ensure proper security)

The server on the receiving end (the one which relies heavily on the same-origin policy for protection) can then authorize the request by including an Access-Control-Allow-Origin header which contains either * or the value of the request's Origin header.

If it doesn't, the browser simply discards the response and returns an error to the Javascript callback. MDN goes into great detail (1) (2) on what goes on under the hood in both scenarios as well as what other headers the target system can set to further relax the security in a controlled fashion. (eg. Allowing access to custom HTTP headers you're setting)

GET requests and the allowed subset of POST requests can already be used for CSRF attacks via other mechanisms unless the target web app is properly protected, so it was decided that there was no benefit to doubling the number of HTTP requests involved in operating services like the Google Font Library.

ssokolow
  • 403
  • 3
  • 10
  • 1
    It's worth noting that CORS wasn't supported by MSFT and Opera because they believed the complexity of CORS was best solved by an authentication token between sites, and not embedded in HTTP headers. Not to mention that CORS allows editing of the header which may cause complications with CSRF token validation. – makerofthings7 Mar 30 '13 at 04:22
  • +1 [You're the only one who mentioned`XDomainRequest` in this site.](http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx) Have you heard anything about UMP (It's simple like XDomainRequest, and from W3C) – makerofthings7 Mar 30 '13 at 04:25
  • No. I tend to focus on things that are or may eventually be of practical use to me and UMP never reached critical mass for it to come to my attention until you mentioned it. As far as I can tell, the widespread implementation of CORS seems to have stolen all its inertia. – ssokolow Mar 31 '13 at 06:36
1

Actually in this case, the origin of Google analytic script is a.com

Quote from JavaScript: The Definitive Guide:

It is important to understand that the origin of the script itself is not relevant to the same-origin policy: what matters is the origin of the document in which the script is embedded.

schroeder
  • 123,438
  • 55
  • 284
  • 319
nandin
  • 109
  • 2
  • 2
    "_origin of the script itself is not relevant to the same-origin policy_" but it is: first, the script `http://a.c/s.js` is fetched with its HTTP origin `http://a.c`, with its `a.c` HTTP cookies; then run within the scope of the HTML document (`http://b.c/doc.html`) HTTP origin (`http://b.c/`). – curiousguy Jul 14 '14 at 00:39