Same Origin Policy: Demystified
Tags: security, webdev, programming, cyb
Published: 2021-03-04 08:22:49

The original article is here: [Same Origin Policy on AppSec Monkey](https://www.appsecmonkey.com/blog/same-origin-policy/).

## What is the Same Origin Policy?
*Same Origin Policy* is a set of design principles that govern how web browser features are implemented.

Its purpose is to isolate browser windows (and tabs) from each other so that, for example, when you go to example.com, the website will not be able to read your emails from gmail.com, which you may have open in another tab.

## What is an Origin?
The definition of an origin is simple. Two websites are of the same origin if their scheme (http://, https://, etc.), host (e.g., www.appsecmonkey.com), and port (e.g., 443) are the same. You can find the definition in [RFC6545 - The Web Origin Concept](https://tools.ietf.org/html/rfc6454).

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uxk3ggproqcbue90mh1l.jpg)

### Implicit ports
If the port is not explicitly specified, it's implicitly 80 for `http` and 443 for `https`.

### Examples

These URIs are considered to be of the *same* origin: 

- `https://www.appsecmonkey.com/`
- `https://www.appsecmonkey.com/blog/same-origin-policy/`
- `https://www.appsecmonkey.com:443/blog/same-origin-policy/`

And these are all of *different* origins: 

- `http://www.appsecmonkey.com/`
- `https://appsecmonkey.org/`
- `https://www.appsecmonkey.com:8080/`


## What is allowed by the same-origin policy, and what is not?

In general, writing is allowed, and reading is denied. How exactly this applies depends on the browser feature, so let's see some examples.

## JavaScript Window Access

There are many ways in which a website can get a handle to another window. However, you can restrict this by using a [COOP (Cross-Origin Opener Policy)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy) and the `frame-ancestors` directive of [CSP (Content Security Policy)](https://www.appsecmonkey.com/blog/content-security-policy-header/).

These methods include: 

- Using `window.open`.
- Creating a frame (like we're about to).
- Using `window.opener` if the website is framed by another.
- Received postMessage `event.source`.

This handle provides access to stripped-down versions of the `window` and `location` objects.

Let's run some experiments on http://a.local. We'll start by getting a window handle to http://b.local by creating a cross-origin frame like so: 

```js
var crossOriginFrame = document.createElement("iframe");
crossOriginFrame.src = "http://b.local";
document.body.appendChild(crossOriginFrame);
var handle = crossOriginFrame.contentWindow;
```

Now let's see what we can do with it.

### ❌ Not allowed: Read cross-origin content

```js
console.log(handle.document.body.innerHTML);
❌ Uncaught DOMException: Blocked a frame with origin "http://a.local" from accessing a cross-origin frame.
```


### ❌ Not allowed: Write cross-origin content

```js
handle.document.body.innerHTML = "<h1>Hacked</h1>";
❌ Uncaught DOMException: Blocked a frame with origin "http://a.local" from accessing a cross-origin frame.
```


### ✅ Allowed: Read the number of frames within the cross-origin window

```js
console.log(handle.frames.length);
✅ 2
```

**☠ Security Impact**: 
Being able to count the frames enables the [frame counting](https://xsleaks.dev/docs/attacks/frame-counting/) cross-site leak attack.

### ❌ Not allowed: Read cross-origin URI

```js
console.log(handle.location.href)
❌ Uncaught DOMException: Blocked a frame with origin "http://a.local" from accessing a cross-origin frame.
```

### ✅ Allowed: Write cross-origin URI
```js
handle.location.replace("https://www.example.com");
```

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ueg7fwkiehxl1dy9fu6.png)

**☠ Security Impact**: 
Websites that you frame on your website can get a window handle to it via the [window.opener](https://developer.mozilla.org/en-US/docs/Web/API/Window/opener) property. This means that if you load a malicious website in an iframe on your website, the frame can change the URI of your site into, e.g., a phishing page (clone of your page that, e.g., steals your users' passwords or makes them download something malicious). You can prevent this using [sandboxed iframes](https://www.w3schools.com/tags/att_iframe_sandbox.asp).

### ✅ Allowed: Messaging to the window via postMessage
The [postMessage](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) method allows cross-origin windows to communicate with each other.

```js
// on http://b.local/
window.addEventListener("message", (event) => {
    document.write("Got message: " + event.data)
}, false)

// on http://a.local/
handle.postMessage("hello", "http://b.local");
```

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cmndcda1xrc80gs2t2xu.png)


### ❌ Not allowed: Read localStorage or sessionStorage
```js
console.log(handle.localStorage);
❌ Uncaught DOMException: Blocked a frame with origin "http://a.local" from accessing a cross-origin frame.

console.log(handle.sessionStorage);
❌ Uncaught DOMException: Blocked a frame with origin "http://a.local" from accessing a cross-origin frame.
```

## Resource Embedding and JavaScript Access
In general, embedding any resource (image, style, script, etc.) is allowed cross-origin, but JavaScript cannot directly access the resource. However, you can restrict this with a [CORP (Cross-Origin Resource Policy)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cross-Origin_Resource_Policy_(CORP)).

Furthermore, when embedding resources, the browser user's cookies for the embedded resource's site are sent along with the request. Effectively this allows websites to send credentialed (with cookies) cross-site GET and HEAD requests.

**☠ Security Impact**: 
The fact that browsers send cookies along with these requests enables [CSRF (Cross-Site Request Forgery)](https://www.appsecmonkey.com/blog/csrf-attack-and-prevention/) attacks if your website allows performing actions (e.g., transfer money, change password, delete account) via GET requests (which it, of course, shouldn't).

Let's see a couple of examples of cross-site resources.

### ✅ Allowed: Displaying an image
```html
<img id="cross-origin-image" src="http://b.local/monkey.png"/>
```

### ✅ Allowed: Create a canvas from the image

```js
var crossOriginImage = document.getElementById("cross-origin-image");
var canvas = document.createElement("canvas");
canvas.width = crossOriginImage.width;
canvas.height = crossOriginImage.height;
canvas.getContext('2d').drawImage(crossOriginImage, 0, 0, crossOriginImage.width, crossOriginImage.height);
document.body.appendChild(canvas);
```

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/et56pw8ildiqo53jnno0.png)

### ❌ Not allowed: Read pixels from the canvas

```js
canvas.getContext('2d').getImageData(1, 1, 1, 1).data;
❌ Uncaught DOMException: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data.
```

### ✅ Allowed: Loading a style
This is fine. The style will be rendered on the page.
```html
<link rel="stylesheet" href="http://b.local/test.css"/
```

### ❌ Not allowed: Read the style contents

```js
console.log(document.styleSheets[0].cssRules);
❌ Uncaught DOMException: Failed to read the 'cssRules' property from 'CSSStyleSheet': Cannot access rules
    at <anonymous>:1:25
```

### ✅ Allowed: Loading a script
```html
<script id="cross-origin-script" src="http://b.local/test.js"></script>
```

Here is the content of `test.js`: 

```js
var x = 5;
```

### ❌ Not allowed: Read the script source
There is no way to get the source of the script.


### ✅ Allowed: Access data and functions provided by the script
The script had the `x` variable, remember? We can use it now on our page.

 ```js
 console.log(x);
 5
 ```

 This is essentially how [JSONP](https://en.wikipedia.org/wiki/JSONP) worked (don't use it anymore, it was never a good idea, and these days we have better ways which you'll see in a minute).

**☠ Security Impact**: 
The fact that browsers allow access to the data/functions provided by cross-domain scripts enables XSSI (Cross-Site Script Inclusion) attacks if your website serves dynamic JavaScript files with authenticated user data in them. So don't do anything like that.

## HTML forms

In the previous section, we looked at how embedding cross-origin resources allowed for malicious websites to send credentialed GET requests on the browser user's behalf. Now you will see how HTML forms make it possible to send credentialed *POST* requests.

**☠ Security Impact**: This behavior is the primary reason [CSRF](https://www.appsecmonkey.com/blog/csrf-attack-and-prevention/) vulnerabilities are so prevalent. Luckily the situation is finally going to improve soon as [SameSite Cookies](https://www.appsecmonkey.com/blog/samesite-cookies/) are starting to be enabled by default.

### ✅ Allowed: Submit credentialed cross-origin urlencoded HTML form

Let's say we have the following form on http://a.local and the user has an active session on http://b.local: 
```html
<form method="POST" action="http://b.local/transferFunds">
    <input name="amount" type="text" value="10000"/>
    <input name="iban" type="text" value="HACKERBANK1337"/>
    <input type="submit" value="Send"/>
</form>
```

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3p61owe4g4g4ry5epypv.png)

When the user clicks the "Send" button, a HTTP request like this is sent to http://b.local: 

```yaml
POST /transferFunds HTTP/1.1
Host: b.local
Cookie: SESSIONID=s3cr3t
Content-Type: application/x-www-form-urlencoded
...
amount=10000&iban=HACKERBANK1337
```

And the unwitting web application would send the money, thinking the request came from the user.

### ✅ Allowed: Submit credentialed cross-origin multipart HTML form
A cross-origin multipart form can be submitted without problems. Just add the `enctype` parameter like so: 
```html
<form method="POST" action="http://b.local/transferFunds" enctype="multipart/form-data">
    <input name="amount" type="text" value="10000"/>
    <input name="iban" type="text" value="HACKERBANK1337"/>
    <input type="submit" value="Send"/>
</form>
```

### ❌ Not allowed: Submit credentialed cross-origin JSON HTML form
Specifying *application/json* for the `enctype` will not work. The browser will fallback to *application/x-www-form-urlencoded*.
```html
<form method="POST" action="http://b.local/transferFunds" enctype="application/json">
    <input name="amount" type="text" value='{"amount": 1000, "iban": "HACKERBANK1337", "foo": "'/>
    <input name="amount" type="text" value='bar"}'/>
</form>
```

**☠ Security Impact**: If the application fails to validate the content type properly, it could interpret this kind of POST request as valid JSON. Also, there are some drafts about implementing `enctype="json"`, although no browser currently does so. For these reasons, it's vital to implement CSRF protection for, e.g., REST APIs as well as traditional web applications if they use cookie-based session management.

## XHR and Fetch requests

### ✅ Allowed: Sending credentialed cross-origin GET, HEAD, and POST requests with XHR

The following will work. You will get an error, but the request will be *sent*. You can verify with your browser's developer tools, or better yet, set up a proxy tool such as [OWASP ZAP](https://www.zaproxy.org/) between your browser and the webserver to really see what's going on.

```js
let xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('GET', 'http://b.local/');
xhr.send()
```

### ✅ Allowed: Sending credentialed cross-origin GET, HEAD, and POST requests with fetch

Using fetch will work just the same.

```js
fetch('http://b.local/', {method: 'POST', credentials: 'include'});
```

### ❌ Not allowed: Inspect the XHR response

With either, XHR or fetch, you will not be able to read the response that you get.

```js
❌ Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://b.local/. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).
```

I'll get to what the `Access-Control-Allow-Origin` thing is in a minute.

### ❌ Not allowed: Sending PUT, PATCH, DELETE, etc. requests

Only specific HTTP verbs are allowed by default (GET, POST, HEAD, and OPTIONS).

```js
fetch('http://b.local/', {method: 'PUT', credentials: 'include'});
❌ Access to fetch at 'http://b.local/' from origin 'http://a.local' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
```

There are two interesting parts in this error. It's talking about a preflight request, and the request mode. I'll get to the prefight thing soon, but let's quickly look at request modes first.

### Request modes
The [request mode](https://developer.mozilla.org/en-US/docs/Web/API/Request/mode) can be used by web applications to prevent accidentally leaking unnecessary data in a request by, e.g., setting the mode explicitly to same-origin.

It, however, cannot be used to bypass any security controls. For example, if we change the mode to 'no-cors' like described in the error message, the PUT request would still not be sent; it would just result in a different error.
```js
fetch('http://b.local/', {method: 'PUT', credentials: 'include', mode: 'no-cors'});
❌ Uncaught (in promise) TypeError: Failed to execute 'fetch' on 'Window': 'PUT' is unsupported in no-cors mode.
```

### ❌ Not allowed: Sending JSON requests

Only the whitelisted content types are allowed. This won't work.

```js
fetch('http://b.local/', {
    method: 'POST', credentials: 'include', headers: {
        'Content-Type': 'application/json'
    },
});
❌ Access to fetch at 'http://b.local/' from origin 'http://a.local' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
```

Again with the preflight. Alright, now I'll tell you what these `Access-Control-Allow-` things are.


## Cross-Origin Resource Sharing (CORS)
[Cross-Origin Resource Sharing](https://web.dev/cross-origin-resource-sharing/), or CORS for short, is a mechanism for a website to partially opt-out of the same-origin policy in a controlled way.

### Access-Control-Allow-Origin
For example, if http://b.local *wants* http://a.local to be able to read its content via fetch/XHR responses, then by specifying the CORS headers in the HTTP response, it can do so.

```yaml
Access-Control-Allow-Origin: http://a.local/
```

You can also use a wildcard as the origin, but then `Access-Control-Allow-Credentials` (below) cannot be `true`. Also, the wildcard cannot contain any other text, so *.appsecmonkey.com wouldn't work. It's all or nothing, a complete wildcard or an exact origin.

### Access-Control-Allow-Credentials

By default, CORS doesn't allow credentialed requests (that include the browser user's cookies). After all, credentialed CORS requests effectively give the websites to whom the privilege is granted full read and write control of the browser user's data in the application.

If you still want to enable it, you can use the `Access-Control-Allow-Credentials` header like so: 

```yaml
Access-Control-Allow-Credentials: true
```

### Access-Control-Allow-Headers

Then, if you want to allow JSON requests or other non-whitelisted headers/values, you can do so via the `Access-Control-Allow-Headers` header: 

```yaml
Access-Control-Allow-Headers: content-type
```

### Access-Control-Allow-Methods

Finally, if you want to enable other HTTP verbs than GET, POST, HEAD, and OPTIONS, you have to use the `Access-Control-Allow-Methods` header: 

```yaml
Access-Control-Allow-Methods: GET, POST, HEAD, PUT, PATCH, DELETE
```

### Preflight

GET, POST, and HEAD requests are always *sent*, but the website is forbidden access to the response data if the response doesn't contain appropriate CORS headers.

But how does the browser know whether it is allowed to send a PUT request or not? If the answer to the question "can I send a PUT request" is in response to the PUT request, doesn't this create a chicken and egg problem? That's a great question, and the answer is simple: we send two requests.

The browser sends an `OPTIONS` request *first*, and then looks at the response headers for that request. If `PUT` is allowed (in `Access-Control-Allow-Methods`), only then the actual PUT request is sent.

This first OPTIONS request is aptly named the [preflight request](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request).

### CORS in action

Let's revisit one of the tests we made earlier, but this time, http://b.local/ returns the following HTTP response headers: 

```yaml
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: content-type
Access-Control-Allow-Methods: GET, HEAD, POST, PUT
Access-Control-Allow-Origin: http://a.local
```

### ✅ Allowed: Sending credentialed cross-origin GET, HEAD, POST, and PUT requests with fetch and reading the response

Now we have complete control of the cross-origin page.

```js
fetch('http://b.local', {method: 'PUT', credentials: 'include', headers: {
        'Content-Type': 'application/json'
    }}).then(function (response) {
	return response.text();
}).then(function (html) {
	// This is the HTML from our response as a text string
	console.log(html);
});

<!doctype html>
<html lang=en>

<head>
    <meta charset=utf-8>
    <title>b.local</title>
    ...
```

**☠ Security Impact**: If you specify CORS headers like this, you are giving the allowed origins complete control over your website, including any authenticated user data and functionality. The same-origin policy is there to protect you, so think carefully before opting out of it.

## WebSockets

### ✅ Allowed: Opening a cross-origin WebSocket connection, reading from it, and writing to it
This may be surprising, but the same-origin policy does not restrain WebSockets.

**☠ Security Impact**: If the application using WebSockets doesn't validate the `Origin` header in the WebSocket handshake or implement some other CSRF protection mechanism, it will be possible for a malicious website to open a WebSocket connection and use it as the browser user.

## Conclusion
The same-origin policy is at the root of the web browser security model. It's old, and it's not perfect. As such, developers must understand the risks and implement the proper defense measures in their applications.

Generally, writing is allowed (e.g., sending cross-origin POST requests), but reading is not (e.g., reading the response to those requests). This means that without CSRF protection, websites are in trouble.

Developers can partially relax the same-origin policy with the CORS (Cross-Origin Resource Sharing) headers, but they should do so with care and avoid CORS altogether if possible.

The same-origin policy can also be made tighter in some of the newer browsers via CORP (Cross-Origin Resource Policy) and COOP (Cross-Origin Opener Policy).

Finally, and somewhat surprisingly, WebSockets are not protected by the Same Origin Policy at all. This can have surprising and unpleasant effects if you're not careful when implementing them.

## Get the web security checklist spreadsheet!
[![Subscribe](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3iqd1ef4a5a0icyvvk65.jpg)](https://eepurl.com/hqAGt5)
 ☝️ [Subscribe](https://eepurl.com/hqAGt5) to AppSec Monkey's email list, get our best content delivered straight to your inbox, and <b>get our 2021 Web Application Security Checklist Spreadsheet for FREE</b> as a welcome gift!

## Get the web security checklist spreadsheet!
[![Subscribe](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3iqd1ef4a5a0icyvvk65.jpg)](https://eepurl.com/hqAGt5)
 ☝️ [Subscribe](https://eepurl.com/hqAGt5) to AppSec Monkey's email list, get our best content delivered straight to your inbox, and <b>get our 2021 Web Application Security Checklist Spreadsheet for FREE</b> as a welcome gift!

## Don't stop here
If you like this article, check out the other application security guides we have on [AppSec Monkey](https://www.appsecmonkey.com/) as well.

Thanks for reading.