Have you ever wondered what would have happened if by just opening a link on the browser, your data from another website was compromised? All hell breaks loose!
Thanks to the Same Origin Policy in browsers that cannot happen this easily. Before I dive into how it works and explain other related mechanisms implemented by browsers, we have to have some understanding of the browser DOM.
What is DOM?
DOM stands for Document Object Model, it is a programming API for HTML and XML documents.
It represents the web pages in a tree-like data structure.
When a page loads, the browser creates the DOM for that webpage with the specified elements.
DOM basically consists of anything you see on a webpage, all the elements, texts, images, literally every content. DOM also includes the cookies, localStorage, sessionStorage, etc
You can see the DOM created for this blog by clicking the inspect tab.
So now it is pretty vivid that if an attacker somehow gets access to your DOM (imagine some application where you are logged into, got authentication cookies, and have your personal information loaded there) he owns everything.
He can read any of your page content, does the same actions that you can do with your account, and so on.
Demystifying two example scenarios
Scenario 1
- A user is logged-in to their mail client.
- The domain (full origin) is http://mail.tld/inbox
- The user opens a site in the same browser in a new tab.
- The website has a hidden iframe sourced to the mail client.
- Cookies are being sent automatically for that domain (not considering LAX or SameSite cookies)
Can JavaScript read the iframe content?
No, JavaScript cannot read the iframe content (basically the DOM).
If it could, then it could have extracted all of our emails, cookies, auth token, etc (Whole DOM)
Hacking would have been so simple if it was not restricted to not access to other origins DOM! 😂
Everyone could have created a simple iframe for a targeted webpage. Lure their victims into their webpage and steal their data.
This is seriously against security. So browsers all have this principle built-in.
Scenario 2
- A user is logged-in to their mail client.
- The domain (full origin) is http://mail.tld/inbox
- The user opens a site in the same browser in a new tab.
- The website has some JavaScript code. (running in the background on their browser)
- The JavaScript sends an HTTP request to the mail client. (by user’s browser, on behalf of them)
Does the request have cookies?
Yes, It can have. The Attacker can decide with their JavaScript code.
Who decides if it has cookies?
The Attacker can decide with their JavaScript code.
What does the browser receive?
Exactly the whole email panel and contents. (whole HTML page (DOM) in the HTTP response)
What does javascript receive?
Nothing! (this is where SOP comes into the game. will dive into it in a second)
JavaScript does not have access to the response (even though it is received by the browser)
Same Origin Policy
The Same Origin Policy is a security mechanism that restricts how a script from one origin can interact with a resource from another origin.
An origin is defined as a full combination of:
1. URL Scheme
2. Host
3. Port
Take https://randomSite.com as an example.
This origin is not the same as http://randomSite.com since their schemes are not the same.
So all the three-part of the URL should be the same to be considered the same origins.
Same Origin Policy Exceptions
If you ever built a frontend application or hung around in other peoples webpage sources, you must have seen some library being imported from another origin using <script>
tag.
Something like this for example:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
</head>
<body>
<p>Hello There</p>
</body>
</html>
Or you might have imported an image from other websites to your webpage without actually putting it on your server.
<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
<img src="https://images.unsplash.com/photo-1513564774965-ac25ddf81e1eixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb1.2.1&auto=format&fit=crop&w=1169&q=80" alt="image from unsplash" />
</body>
</html>
But as we explained earlier SOP prevents us from accessing the DOM contents of other origins, how is this possible without any restrictions?
SOP has some exceptions; It does not apply on:
<img>
tags<script>
tags
Cross Origin Resource Sharing
As we discussed, SOP prevents JavaScript to access Cross Origin Resources.
But there are use cases that we want JS to have access to them.
The server can let JavaScript access Cross Origin resources.
Some headers should be set by the server. (Access-Control-Allow-Origin)
Based on the response headers, the browser’s SOP decides:
- Give access to DOM (Response body message)
- Prevent access to DOM (Response body message)
One of the good examples where it applies is with Single-Sign-On Authentication. Configuring CORS in a proper way is crucial in this authentication mechanism to move around the authentication to different domains.
I will talk about SSO and some of its implementations in future blogs.
SOP in Action
Create an empty HTML file and copy this code into it.
Make sure you are logged-in to your gmail or any other email services but change the url of the code.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>SOP in Action</title>
</head>
<script>
const stealEmails = () => {
var xhr = new XMLHttpRequest();
var url = 'https://mail.google.com/mail/u/0/#inbox';
xhr.onreadystatechange = () => {
if (xhr.readyState == 4 && xhr.status == 200) {
alert(xhr.responseText); }
}
xhr.withCredentials = true;
xhr.open('GET', url, true);
xhr.send();
}
</script>
<body onload="stealEmails()">
<h1>Cross Origin Request from your localhost to your mail account</h1>
</body>
</html>
Now open the file in the browser, whether by serving it with your localhost (you can use live server extension in VScode or SimpleHTTPServer python built-in) or simple as a file, doesn’t really matter.
Now see your browser console.
Code explanation:
-
We created a new instance of
XMLHttpRequest
Class which allows us to make HTTP requests in JavaScript. We can use other libraries too. -
onreadystatechange
is an event handler that is called whenever thereadState
attribute changes.
The callback we assign to it then will be called as soon as this state changes. -
readyState
andstatus
properties can have some default values which indicate the state of the request.
Value 4 forreadyState
means the operation is completed and value 200 forstatus
means the received HTTP response code was OK.This way we make sure the request was completed successfully before calling our callback function.
-
withCredentials
property set to true forces our request to contain the cookies associated with that domain automatically since the cookies are not sent by default.By doing this, we can make sure the request has the authentication cookies.
-
Finally, we
open
a new GET request, andsend
it.
The request is actually really sent and the response is also received by the browser, but the security mechanism implemented in our browsers checks the response headers.
If the origin initiating the request is allowed to make that request the callback function which tries to access the body response will be executed.
Otherwise, like the one we had in our example, it will not run that portion of the code, preventing a lot of security risks and attacks against users.
References:
https://twitter.com/voorivex/ OWASP Bootcamp
https://developer.mozilla.org/