HCTF线下赛AWD

前言

第一次参加AWD,有幸被师傅带进了线下赛Orz,运气特别好拿了个第四名

虽然事前准备了很多吧,但是真正参加的时候还是有很多手忙脚乱的地方,网上的AWD总结一看一大堆,几乎都是换汤不换药的上waf,文件守护,备份,D盾。少见的可能会放一下搅屎的思路,但其实对这次AWD来说,仅仅知道这些套路用处不是特别大,一些随机应变能力,选手的知识积累和经验,才是这场AWD真正需要的,所以这篇文章就不讲AWD的规则和常见套路,只讲这次碰到的坑和比较少关注的Trick

赛前

毕竟是有赞助的比赛,机票能报销一部分特别舒服,赶着没课还顺便看了一趟女票,比赛现场很多很多零食饮料,但大家都在拿肥宅快乐水和士力架和薯片,下次AWD记得先抢这些XD

这次比赛环境需要通过有线连接,Mac需要自带转接头,另一个队的师傅到下午才借到转接头,血亏

比赛也提供的外网,但是需要连入现场的Wifi,所以就会存在有线和无线并存的情况,一开始我不知道怎么办,每次需要外网的时候就去扯网线连Wifi,但一扯的时候脚本和SSH就全崩了,后面潘师傅告诉我可以用静态路由让电脑同时连内网公网,orz

比赛有个平台,上面是显示有每一轮还有多久结束,已经当前各个队伍服务是否正常,如果有队伍被拿flag,旁边的通知栏会立马弹消息 比如: Redbud攻陷了天枢的Web1 --09:54:20

所以可以通过平台看到自己的服务有没有被打,比赛途中几乎是一直在看通知栏

比赛规则

rule.jpg
hctf.png
Flag通过Json形式上传到指定接口,现场写脚本

import os
import json
import requests
import time
#-------------------------------------
token = '9ec7db71473995e65a672f8878a5910dd07df6c2'
url = 'http://192.168.200.150:8005/api/team/submit/' + token
dictory = './flag/'
sleep_time = 120                   #秒
#-------------------------------------
files = os.listdir(dictory) #列表形式
def submit():
    for file in files:
        with open(dictory+file) as flag_txt:
            flags = flag_txt.readlines()
            for flag in flags:
                flag = flag.strip()
                dic = {'flag':flag}
                json_flag = json.dumps(dic)
                print(json_flag)
                try:
                    res = requests.post(url,data=json_flag,headers={'Content-Type':'application/json'},timeout=1)
                    print(res.text)
                except:
                    print('连接失败')
                    continue
while True:
    submit()
    time.sleep(sleep_time)

其实一开始脚本没这么复杂的,至于为什么这么复杂,接下去会说

比赛前半小时的时候,主办方给了一张纸条,上面会有每个队伍的SSH账号密码,一般一个服务对应一个SSH,第一天Web只有一个服务,所以我只有一个SSH,但是比赛没开始的时候是没网的,所以是连不上去的,比赛开始的时候才能连(我刚开始一直以为是我Mac的原因)

SSH

赛时

第一天

由于我们队只有我一个Web,我又是第一次打线下,所以比较慌,导致一开始怎么都连不上去,后面发现我输入的IP是PWN的IP....

这个时候就需要多年的手速,迅速连上SSH,然后赶紧文件守护,流量监控,备份

HCTF的比赛质量肯定不会给我们root,我拿到的是低权限用户,啥命令都用不了,机器是ubuntu18最新,不能提权,然后这里有个史诗级大坑,ubuntu18自带的是python3而不是python2,而且我不是root不可能apt一个python2出来,但是我的所有脚本全是py2的,但是又不可能不上流量啊,于是我花了十几分钟,一句一句把print xxxx改成了print(xxxx)

在我刚改了一会儿的时候,Nu1L已经web一血了,我差不多就知道了肯定有预留后门,但是Web只有我一个人,只能慢慢改完了脚本,上好,才开始去看D盾的扫描结果,这时候比赛已经快进行了二三十分钟了

第一个洞

D盾结果发现果不其然一个预留后门,赶紧删了,随便在浏览器测了一下,发现还有几个队没有删,于是准备写一个脚本批量打,这时候第二个史诗级巨坑出现了

这次的比赛,每个队伍的Web服务都被映射到了同一个IP,每个队伍的Web服务所在IP一样但端口不一样,

例如我的Web服务是192.168.100.100:5011,另一个队就可能是192.168.100.100:5005这样的

但是我准备的攻击脚本是根据IP来区分的,端口是固定的,根本不适用这次比赛,现场重新写肯定是来不及的,Web就没人维护了,可能就直接GG了,我又害怕预留后门马上就要被删光了,肯定要趁现在多拿点flag,于是我开始手动在浏览器中打那些没删后门的队伍。。。。。

后面除了NPC后门几乎都删了,于是我赶快改了我的脚本,终于差不多可以固定ip,端口为变量了,这时候我发现我又被打了

第二个洞

查看流量,发现

192.168.100.100:5011/client/user/index.php?img=PHP://filter/convert.url-encode/resource=../../../../../../../flag
和
192.168.100.100:5011/client/user/index.php?img=/flag

打了很多很多很多次,怀疑,查找对应文件,最末尾发现

if ($_GET['img']) {
    include($_GET['img'])
}

文件包含,但是当我在浏览器访问这个exp时,浏览器显示无限重定向,用python的requests也拿不到flag,于是我以为这个exp是假的,但是我还是一直被打,并且流量只有这一个exp,想了许久,决定Burp抓一下包,结果一个大大flag,类似于这样

burp

后面我才知道,requests会自动302,但是苦于当时不知道怎么阻止302,于是我又开启了手动burp拿flag的骚操作。。。

正确示范

requests.get(url,allow_redirects=False)

小擦曲

我用的是柠檬师傅的流量监控,但是里面自带了通防waf,比赛规则上写了禁止使用通防,但是我是不知情的,于是第一次,杭电的师傅下来警告我上了waf,然后我感觉把waf中的对url传参的过滤黑名单全删了,结果柠檬师傅在另一处还留了一个文件上传的通防,然后杭电师傅第二次下来。。。。。扣了800分,瞬间垫底

第三个洞

一个没啥过滤的文件上传,就不细说了,然后我写的攻击脚本只是针对URL传参的攻击,上传文件的话还必须要二次访问才能利用,不死马的话需要三次访问才能利用,所以脚本比较垃圾,这次回来以后需要在改进一下

第四个洞及以后

因为第二个洞的原因,我第一天一直在手动打手动交flag。。所以几乎没有时间审计,exp全靠流量,抓到直接打回去就是了,如果有flag就赶紧修然后写脚本继续打,所以漏洞的原理成因我都没去看,反正能打就行了,但是第一天还是一直垫底。

晚上

img文件包含的洞打了一天,CNSS到第一天结束都没有修

回去以后找到了如何防止302的方法,然后因为我的脚本不是攻击拿到flag直接提交,而是拿到flag写入文件,所以我写了另一个脚本去访问这些文件再提交,但是我发现一个脚本要对应一个flag文件,所以我的flag提交脚本是遍历一个文件夹下的所有文件,获取每一行的内容自动提交。

但是这样有很多缺点,因为每次脚本写入的时候肯定不是以追加的方式,于是重新写入的时候,文件会清空,于是会出现提交flag脚本访问flag文件,flag文件刚好被清空的情况,为了解决这个问题,必须增大访问频率于是一开始我将提交flag脚本设置了while True: submit()

然后立马被杭电师傅请喝茶了。。。

后面提交flag的接口还把我这个IPban了。。。。。。。。。。

没办法只好改成了120s 运行一次

晚上把第一天的所有洞都改成了自动化打自动化提交,本来想着完美,结果第二天全修了,但是还是得打,因为可能会有队伍恢复备份,如果是最开始的备份,可以卡这段时间打,第二天就有很多队伍恢复备份的那小段时间被疯狂打,所以恢复备份的话还是要先修完洞在上线

第二天

第一天下午PWN就有一血了,但是真正PWN开始是主力的时候应该还是第二天,Web1 第二天除了天枢都全down了,这个时候我们才发现这次比赛审洞打全场并不是最好的拿分手段,其实不被down才能最快拿分,因为比赛规则为,如果一轮某支队伍服务DOWN,则其他队伍打这支队伍不会获得任何分数,这支队伍被打也不会扣任何分,DOWN机分为60分/每轮,由没down的队伍平分,一个flag为10分,也就是说如果你服务down了,每轮必须打6支没有down的队伍才能持平,而且这次比赛的check机制十分十分十分十分十分严厉,web1几乎全场都是down的,chmd5恢复最原始的备份一开始也被check了,可以说这次的check真的是全方面不留死角的check,于是第二天我的目的就从攻击转变为了防守,第二天放出来web2,宁愿被打也要死保服务,最后web2全场只有4支队伍没有down,我靠着服务正常,每轮拿其他8支队伍的60*8/4 check扣的分,一个上午从最后一名直升第六。

web1因为全场都是down的(除了天枢)所以打web1是不拿分的,于是第二天几乎全是围绕web2,被6支队伍打扣得分才和down机分相同,所以宁愿被打也不能选择down,check的严厉程度也不能直接删文件,删功能,例如web1的第二个洞include($_GET['img']) 其实是一个正常功能,直接注释就会被check,需要自己对这个功能写过滤,以及杭电200个印度人的check,还会手动登入你的ssh查看你的文件和流量脚本,真的是很严格了很辛苦了

最后靠着别人的exp以及死保的服务,还有另外两个pwn爷爷,最终拿到了第四名

paiming.jpg

后记

在最后的时候好像又有很多队审出了洞,但比分已定而且我已经没什么欲望继续打下去,最终整场比赛全靠着捡洞,流量真是个好东西