Understanding Same-Origin Policy (SOP)

What Is Same Origin Policy

When looking to implement a new chunk of JavaScript or exploring credential exfiltration for bug bounty, we definitely run into CORS (Cross-Origin Resource Sharing). In order to understand CORS, it is important to understand Same-Origin Policy (SOP). This core security feature, built into every browser, controls how scripts or documents from one origin can interact with resources from another. It’s a critical element in preventing malicious attacks like Cross-Site Scripting (XSS) and data theft.

But how does SOP really work? How does it determine ‘same origin’? And what implications does it have on the functionality and security of your web applications? Let’s dig in.

CORS will be explored deeper in subsequent posts.

Setup

To demonstrate the concepts, I am going to be using the following domains/origins:

  • https://jhgfdsa.com/soptestpage.html
    • Cloudflare Pages
  • https://api.jhgfdsa.com
    • Cloudflare Worker that returns a JSON object with random GUID
  • https://jhgfdsa.com/api
    • Cloudflare Worker that returns a JSON object with random GUID
  • https://hydrohousehold.com

Note: I keep the content for these domains gated as I do wacky testing on them. As such, they may or may not be online (or at least accessible) and will most likely not have the content as represented in this blog post as I have most likely torn them down to do some other wacky experiment.

Understanding the Origin

What exactly is an “origin”? In web terms (and defined in the RFC), an origin is defined by the combination of a URL’s scheme (protocol), hostname, and port. This trio determines the “address” of web resources and establishes the boundaries for the Same-Origin Policy (SOP), which governs interactions between different origins to maintain security.

shows same origin being allowed to work due to same-origin policy while cross origin throws an error

Key note here is that SOP is a client side defense. It is up to the browser (or other client) to enforce the SOP. This ensures that only trusted resources from intended domains are loaded and have access to sensitive content such as credentials.

Scenario Scheme Hostname Port Same Origin?
https://jhgfdsa.com/home vs https://jhgfdsa.com/about https jhgfdsa.com 443 ✅ Yes
https://jhgfdsa.com vs http://jhgfdsa.com https vs http jhgfdsa.com 443 vs 80 ❌ No
https://jhgfdsa.com vs https://assets.jhgfdsa.com https jhgfdsa.com vs assets.jhgfdsa.com 443 ❌ No
https://jhgfdsa.com vs https://hydrohousehold.com https jhgfdsa.com vs hydrohousehold.com 443 ❌ No
https://jhgfdsa.com:443 vs https://jhgfdsa.com:8443 https jhgfdsa.com 443 vs 8443 ❌ No

Resources with identical scheme, hostname, and port are considered to be from the same origin. For instance, if you’ve two web pages with the URL “https://jhgfdsa.com/home” and “https://jhgfdsa.com/about“, they’re from the same origin as they share the same protocol (https), domain (jhgfdsa.com), and port (implicitly 443 for https).

Alternatively, variations in either protocol (https vs http), domain (jhgfdsa.com vs whatever.com), subdomain (assets.jhgfdsa.com vs jhgfdsa.com), or port (http/80 vs https/443) delineate different origins. Before implementing CORS, it is super important to understand SOP. Without the base understanding, there is a big risk of opening up end users to XSS and CORS related issues.

Same-Origin Policy (SOP) – What is the Point?

The Same-Origin Policy (SOP) is a browser security feature designed to stop malicious websites from executing cross origins attacks. The goal of SOP (or CORS) is to only allow a client side browser to interact and send sensitive information to trusted endpoints. Here’s an example:

Example: Without SOP

You’re logged into your bank at https://securebank.com. Your session cookie keeps you authenticated, so you don’t need to log in repeatedly. Now imagine you visit a malicious site, https://badguy.com. The attacker’s site https://badguy.com runs JavaScript like this:

javascriptCopy codefetch('https://securebank.com/api/account-details', {
credentials: 'include',
}).then(response => response.json())
.then(data => console.log(data));

Here’s what’s happening:

  • Your browser sends the request to https://securebank.com, including your session cookie.
  • Without SOP, the browser allows https://badguy.com to see the response from your bank.
  • The attacker now has access to your account details.

Basically, without the SOP (or properly configured CORS) you are pwned. If you get lured to a malicious website and that website knows you bank at https://securebank.com, they can get your details and potentially auto transfer the money from the account.

How SOP Fixes This

SOP ensures that only JavaScript running on https://securebank.com can read responses from https://securebank.com (because these are literally the same – same protocol, same site, same port). When the browser sees that https://badguy.com is trying to access a different origin (securebank.com), the browser blocks the response entirely.

In the example above (but now with SOP):

  • The malicious request still reaches the bank.
  • The bank might even process the request.
  • But the browser blocks the response from being read by JS from https://badguy.com.
The CORS policy specified on api.jhgfdsa.com is used by the browser to stop jhgfdsa.com from using the returned content

The response (if one is received) is effectively “walled off” by the Same-Origin Policy, so the JavaScript from https://badguy.com cannot use it. Even if the request is made and the results are returned to the browser, the bad actor (in this case JS on https://badguy.com) is stopped from being able to see the sensitive data.

Why Does This Matter?

SOP keeps control in the hands of the origin hosting the sensitive data (the people controlling the API). It doesn’t prevent every attack but it is a great defense stopping phishing type attacks. if you have Cross-Site Scripting (XSS), all bets are off – XSS allows the code to look like it is being ran from origin – in the case of stored XSS it actually is.

What Does SOP Really Lock Down?

The Same-Origin Policy (SOP) is the browser’s way of saying, “Stay in your lane.” It doesn’t block everything, but it ensures that certain interactions between origins are restricted. Here’s what SOP actually locks down:

  • Cross-Origin Reads
    • fetch and XMLHttpRequest:
      • SOP prevents a script on https://badguy.com from reading the response of an API request to https://securebank.com, even if the request is successfully sent.
      • Example: fetch('https://securebank.com/api') will fail unless the server explicitly allows it via CORS.
    • DOM Elements:
      • SOP blocks scripts from accessing content inside an <iframe> that’s loaded from a different origin.
      • Example: A script on https://example.com cannot read the inner HTML of an iframe pointing to https://another.com.
  • Storage Access
    • localStorage and sessionStorage:
      • Each origin gets its own isolated storage. A script on https://badguy.com cannot access localStorage or sessionStorage data from https://securebank.com.
      • Example: If https://securebank.com saves a token in localStorage, it’s completely invisible to other origins.
    • document.cookie:
      • Cookies are scoped to the origin. Scripts from a different subdomain or port cannot access cookies without explicit sharing (Domain attribute).
      • Example: assets.jhgfdsa.com cannot read cookies set for jhgfdsa.com unless configured to share via Domain=jhgfdsa.com.
  • JavaScript and DOM Access
    • Cross-Origin Scripting:
      • SOP blocks JavaScript from interacting with the DOM of a different origin.
      • Example: A script on https://badguy.com cannot call functions or manipulate elements in an iframe pointing to https://securebank.com.
  • Network Requests
    • Cross-Origin Writes (Allowed):
      • SOP allows cross-origin form submissions and some requests, but the response data is blocked.
      • Example: A form on https://badguy.com can send data to https://securebank.com, but it can’t read the response.
    • Cross-Origin Embedding (Allowed):
      • SOP permits embedding resources like images, scripts, and stylesheets across origins.
      • Example: A script from https://cdn.example.com can execute on https://myapp.com, but SOP doesn’t allow it to access localStorage.

Attacks Mitigated by SOP

Attack CategoryHow SOP Mitigates
Cross-Site Data TheftPrevents unauthorized reading of data like cookies, localStorage, sessionStorage, and API responses.
Cross-Origin Resource ManipulationBlocks JavaScript from one origin from manipulating DOM elements of another origin.
Credential ExploitationRestricts unauthorized use of session cookies or authentication tokens across origins.
Cross-Site Scripting (XSS) Data ExfiltrationLimits injected scripts from accessing cross-origin resources.
Cross-Origin Request Forgery (CSRF)Prevents responses from being read in unauthorized cross-origin requests (partial mitigation).
Cross-Origin Storage AccessPrevents reading of localStorage, sessionStorage, or IndexedDB data from another origin.

Loosening the Same-Origin Policy

Relaxing the Same-Origin Policy (SOP) is about controlled exceptions. This can be done via Cross-Origin Resource Sharing (CORS)—the mechanism that lets you define exactly which origins are allowed to access your resources.

Breakdown:

  • CORS Headers: You decide who gets in. Allow a single origin (Access-Control-Allow-Origin: https://hydrohousehold.com), or a controlled list. Wildcard (*) is allowed, but this is reckless.
  • Preflight Requests: For more complex operations (e.g., PUT, DELETE, or custom headers), browsers send a preflight OPTIONS request to confirm the origin is allowed. You’ve probably seen these in your Network tab.
OPTIONS call when the HTTP verb is PUT
OPTIONS call when the HTTP verb is changed to PUT

Practical Security Tips

  • Keep the CORS List Short: Work from the most restrictive as the starting point
  • SOP Alone Isn’t Enough: SOP blocks cross-origin reads but doesn’t stop CSRF. Anti-CSRF tokens are still essential for sensitive data.

Relax SOP where you need to, but keep the controls tight. Loosening it doesn’t mean leaving the door wide open—it’s about balancing security with functionality. I will do a deeper dive into CORS in future posts.

Same-Origin Policy and Iframes

The Same-Origin Policy (SOP) directly influences how iframes can interact with the parent document and other resources. While iframes allow embedding content from different origins, SOP ensures that cross-origin interactions are restricted to maintain security. Basically, the parent document and the content within the iframe are only allowed to communicate if wired up specifically to do so.

Example: Cross-Origin Iframe Restrictions

Here’s the setup to demo this concept:

  • Parent Page: https://jhgfdsa.com/soptestpage.html
  • Iframe Source: https://hydrohousehold.com

The parent page embeds an iframe pointing to https://hydrohousehold.com:

html code showing the hydrohousehold iframe

Attempt to Access Iframe Content

Now, a script in the parent page tries to access the iframe’s content:

const iframe = document.getElementById('hydroIframe');
try {
    const iframeContent = iframe.contentDocument.body.innerHTML;
    const hhhResultElement = document.getElementById('hhhResult');
    hhhResultElement.textContent = iframeContent;
    hhhResultElement.style.backgroundColor = 'lightgreen';
} catch (error) {
    console.error('Error fetching data:', error);
    const hhhResultElement = document.getElementById('hhhResult');
    hhhResultElement.textContent = 'Failed to fetch data. Check the console for details.';
    hhhResultElement.style.backgroundColor = 'lightpink';
}

The iframe.contentDocument.body.innerHTML code attempts to get ahold of the content within the iframe – and the iframe is delivered from a different origin.

What Happens?

Because https://hydrohousehold.com is a different origin than https://jhgfdsa.com, the browser blocks access to the iframe’s contentDocument. The same-origin policy is in effect – what is interesting is that the browser silently blocks (at least within Chromium):

Here’s the page code:

    <h2>Hydrohousehold Iframe</h2>
    <div id="hhhResult">Loading...</div>
    <iframe id="hydroIframe" src="https://hydrohousehold.com" width="600" height="400"></iframe>

And here is the result in the browser:

screenshot showing no CORS error but also no output in the hhhResult because the browser blocked access to the iframe due to SOP

CORS is set to allow access from https://jhgfdsa.com on the API so both of the outputs from same origin and cross origin work. However, the <div id=’hhhResult’> is not populated. In fact, the Loading… gets wiped out because the JS from above is not allowed access to the iframe content. As a result, the text in the hhhResult gets set to null or an empty string.

If you do need to communicate between the iframe and the main page, it is possible to do so with the postmessage mechanism, however, that is a topic for another post.

Conclusion

The Same-Origin Policy (SOP) is a key piece of browser security. It keeps things locked down, preventing your application from accidentally handing over sensitive data to untrusted origins. Understanding SOP is critical before configuring a CORS policy. Understanding SOP and CORS is also critical when working towards that next bug bounty.

Happy hacking!

FAQ

What is the Same-Origin Policy (SOP)?

The Same-Origin Policy (SOP) is a critical security mechanism implemented in web browsers that restricts how documents or scripts loaded from one origin can interact with resources from another origin. An origin is defined by the combination of the protocol, hostname, and port number of a web page. For example, two URLs with different domains or subdomains would be considered different origins, and hence the SOP would prevent them from interacting directly.

Why is the Same-Origin Policy important for web security?

The Same-Origin Policy is vital for maintaining browser security as it helps to prevent various types of attacks such as cross-site scripting (XSS) and cross-site request forgery (CSRF). By restricting how web applications can interact with different , the SOP mitigates the risk of malicious websites accessing sensitive data or performing actions on behalf of users without their consent. This security policy helps to create a safer world wide web by ensuring that scripts from one origin cannot access data or resources from another origin.

How does the Same-Origin Policy work?

The Same-Origin Policy allows scripts running on a web page to make requests to the same origin from which the script was loaded. If the script attempts to access a resource from a different origin, the browser will block the request unless the target server explicitly allows it by implementing Cross-Origin Resource Sharing (CORS) headers. This mechanism defines rules for how resources can be shared across origins, allowing for more flexible interactions while still maintaining security.

What are CORS headers and how do they relate to SOP?

CORS headers are HTTP headers used to allow or restrict resources being requested from different origins. They are integral to enabling Cross-Origin Resource Sharing, which provides a way to relax the Same-Origin Policy by specifying which origins are permitted to

Why does the browser treat subdomains as different origins under the Same-Origin Policy (SOP)?

Subdomains are treated as different origins because SOP enforces strict isolation based on the hostname, along with the scheme and port. This prevents potentially untrusted subdomains from accessing sensitive resources on the parent domain or other subdomains. For example, https://assets.jhgfdsa.com cannot access cookies or storage scoped to https://jhgfdsa.com unless explicitly configured to share them. This isolation protects against attacks like cross-site scripting (XSS) that could exploit weaker subdomain security.

Leave a Reply