RoarCTF2019 Web WriteUp

Dist

打开题目,看到一个build.js泄露了sourceMap

访问build.js.map可以看到API地址和备份文件地址

下载备份文件,可以看到是一个go写的后台,nodejs写得前端。本地跑起来进行审计。

登录处明显有注入

但是题目有个用go写的基于TCP的waf,原理简单来说就是自写了一个TCP转发,并且不允许输入部分关键字。

可以看到是循环读4096个数据,然后过滤掉\r\n\r\n后进行字符串匹配,判断是否含有关键字。
其中有这行语句有问题:
data := bytes.Join(dataS[1:], []byte(""))
join连接时只会连接被\r\n\r\n分割的第二块开始的内容,那么我们填充请求头使得第一块大于4096,第二块恰好没有\r\n\r\n,切割时第二块就只会切割成一份,就不会检查了。

按照下面payload进行盲注'''{"uname":"jrxnm' or (select substr(secret,%s,1)from secret )='%s' and '1","pwd":"qwer1234"}'''
注入出secret,就可以伪造admin了。

这题做到这审了很久都没审出来,赛后师傅们告诉我这后面是今年Teaser CONFidence CTF 2019 “The Lottery” 几乎一模一样的套路,关于go语言 Slice 的特性。

go语言的Slice 用 make([]type, len, cap)进行初始化,它的len代表Slice长度,cap代表容量。当Slice append添加一个数据时,len加1,如果没有大于cap的话cap就不变化,如果大于cap就会动态扩容,如果Slice的容量小于1024个元素,那么扩容的时候slice的cap就翻番,乘以2。

当Slice 赋值时指向的是同一块地址,而如果Slice扩容了,它动态扩容的原理是新开辟一块更大的地址放置。

重点,这意味着,如果有一个切片a内容为[1,2,3],len=3,cap=4。此时令

此时的b, c内容会相等! 为什么呢,因为append的时候len+1没有大于cap就不会扩容,a的位置没变,b,c都是指向原来a的位置。那b,c现在的值是多少呢 [1,2,3,2] 。

当执行完b := append(a,1)时, b指向的仍为a,且的内容为[1,2,3,1],len=4,cap=4。此时a虽也指向[1,2,3,1]但是lem=3,所以值仍为[1,2,3]。当执行c := append(a,2)时,覆盖了a的第四个内容,改为了[1,2,3,2]且此时a,b,c都指向它。

go的内容说完了, 本题每个人只有最多6个容量大小的钱,6次得钱都是通过Beg每次随机从99以内获取

拿到flag的条件是,要么个人拥有的前总额大于999999,要么每次读博自己的钱加上uint64(0xFFFFFFF+rand.Intn(0xFFFFF))等于0x1010010C

我们可以看到个人的balances就是切片结构,按照上面go的特性,当u beg3次后,u.balances为[xx,xx,xx]且len=3,cap=4。此时利用刚才注入的secret伪造admin账户将u加入player中,此时u再beg一次,u.balances内容而为[xx,xx,xx,xx],len=4,cap=4。再利用admin账户start gambling,player.balances 本来指向u.balance,len=3,cap=4,开始后再次append一个

uint64(0xFFFFFFF+rand.Intn(0xFFFFF))覆盖了u.balances的第四个内容,变成了一个很大的数,此时满足个人balances大于999999可以拿到flag。

Easy Upload

拿出代码收集信息是thinkphp 3.2.4写的,这代码审计没发现什么问题,结合thinkphp 3.2.4上传模块发现问题。

关键代码:

可以看到,用户所写得代码使用时未指定files,默认为$_FILES,这意味着,所有$_FILES中的文件都会被上传。而代码只会过滤$_FILES['file']中的文件。所以上传两个文件,一个name为file的正常图片,另一个name为其他的webshell。

最后会打印出$_FILES['file']的文件地址,而不会打印我们shell的地址

简单审计,thinkphp3默认使用uniqid()函数根据时间生成文件名,两个文件上传时间相近可以爆破。

最后上传的php会被后台替换成flag

Easy calc

这上面的代码还是很好绕的,就是还有一个waf,对于所有地址的num参数只允许输入数字和部分符号。绕过这个waf的方法也很简单,把输入的参数改为num即空格+num,waf不会过滤这个参数,但是php识别时这两个是一样的。

此时就比较好做了,只要绕过引号就行

calc.php? num=1;print_r(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)));

easy_java

进题有个help可以下载文件,点击连接输入什么filename都显示文件不存在

原来是需要发送POST数据包,它才会处理这个GET参数(没错,发送POST包处理GET参数)

接下来就简单了,下载配置文件,找到flag相关类

按指定目录结构下载class,丢到工具反编译class就能拿到base64 flag

Online Proxy

拿到手一直以为是个ssrf绕过,绕过了很久都没啥想法。

看到了师傅们的Wp原来是注入Orz,而且和原来的url参数一点关系没有。在X-Forwarded-For处存在Insert Into注入,之后就是盲注了。

主要卡人点在fuzz过程,题目应该是用Cookie中的track_uuid标志每个人,而当本次的IP和上次的IP不同时才会把上次ip插入。

最后就是很常规的盲注。

 

One thought on “RoarCTF2019 Web WriteUp

发表评论

电子邮件地址不会被公开。 必填项已用*标注