Same-Origin Policy (SOP)


What does the Same Origin Policy enforce?

TL;DR The Same Origin Policy (SOP) enforces that websites from different origins cannot access each other's content.

A good explanaition of the SOP by Eric Portis can be found here; in short:


How is the origin defined?

The origin is defined as the triplet of...

SOP does *not* apply to scripts loaded via the <script> tag, they're expected to be static:

This is what we call Cross-Site Scripting Inclusion (XSSI)!
You may find the script here on same-origin-policy.com.

Neither does it apply to embedded stylesheets (in case you wondered):

You may find the stylesheet here on same-origin-policy.com.

SOP *does* apply to iframes (with very few exceptions)...:







Exceptions to the SOP: contentWindow.length, postMessage(), contentWindow.name (see by clicking the button above)

...unless the iframe has the same origin:







SOP also applies to XMLHttpRequests:






We see that we can't even tell the difference between an existing and a non-existing webpage when the SOP does not allow us to read their contents!

Beware: *Writing* across origins is still possible by sending GET/POST requests! This opens up the possibility for CSRF attacks!
Possible fixes for CSRF are:
  1. Use CSRF tokens/nonces that are randomly generated by the server for each user and placed in all forms
  2. Double Submit Cookie: require value in posted content to match value of certain cookie (advantage: no server-side state required, just compare submitted form value against cookie; disadvantage: insecure when an attacker can control a sub-domain!)
  3. Attach a custom headers (like 'X-CSRF-Free') to your XMLHttpRequests. This works because cross-domain requests with custom headers require pre-flight CORS requests!
  4. Same-Site Cookies:
    Strict: never send cookies with cross-origin request (if facebook.com set that, every user following a link there would not be logged in!)
    Lax Mode (default in Chrome since 2021): cookies only send along with safe requests (GET, HEAD, OPTIONS, TRACE) => protects against POST-based CSRF, not against GET-based though
    Examples:
    Set-Cookie: key=value; SameSite=Strict
    Set-Cookie: key=value; SameSite=Lax
    Set-Cookie: key=value; SameSite=None

SOP sort of partly applies to images, we can find out whether an image exists and its size but no more:






Deliberately bypassing the Same Origin Policy:

Sometimes, one wants to deliberately bypass the SOP and communicate cross-origin in mutual consent; there are multiple ways of achieving that (Wikipedia does a good job at listing and summarizing them):

Try out Domain Relaxation:








Try out Cross-document messaging using postMessage():


Send message:
Received messages:

See JSONP in action with the MediaWiki/Wikipedia API:

On this site:
<script src="https://en.wikipedia.org/w/api.php?action=query&list=random&rnlimit=3&format=json&callback=jsonp_callback"></script>

The script included looks somewhat like this: /**/jsonp_callback({ /* ... */ })

Also on this site:
<script>var jsonp_callback = function (response) { /* ... */ }</script>

Result (populated by the jsonp_callback function defined on this site):

(adapted from here)


See CORS in action:

http://same-origin-policy.com/cors_example.php is a useful site that responds with the Access-Control-Allow-Origin: * HTTP response header.
It can also respond with another value for the Access-Control-Allow-Origin header when setting a specific GET parameter, see below.

We can read the result of sending an XMLHttpRequest GET request to http://same-origin-policy.com/cors_example.php because the Access-Control-Allow-Origin: * header is being received:
    

And these are all the response headers received (xmlHttp.getAllResponseHeaders()); we can read those because Access-Control-Expose-Headers: * is also set:

Receiving Access-Control-Allow-Origin: http://same-origin-policy.info instead of Access-Control-Allow-Origin: * should also work:
    

However, when receiving Access-Control-Allow-Origin: http://some-other-site.com in the response, the browser blocks access for us:
    

Note: Feel free to play around with http://same-origin-policy.com/cors_example.php and http://same-origin-policy.com/cors_example.php?access_control_allow_origin=... yourself!