開學后實驗室來了幾個新同學,在線上CTF方面大家一直在持續學習,但AWD模式的CTF我們練習並不多,所以准備搭建一個AWD平台用於實驗室成員的線下賽攻防練習。
最開始的是防災科技大學的線下AWD靶場:
https://github.com/glzjin/20190511_awd_docker
但是靶場沒有計分板和組隊顯示等功能,又找了一下:
https://github.com/zhl2008/awd-platform
騰訊雲服務器沒有FQ,從github上面拉取下來有495MB,本來想從本地電腦上下載后上傳到雲服務器上使用 unzip 命令進行解壓,但 unzip 的速度也很慢,重新尋找解決辦法:
https://zhuanlan.zhihu.com/p/112697807
如果只是為了clone加速,完成到第四步就可以了:
我的碼雲拉取地址在:
https://gitee.com/Cl0udG0d/awd-platform
接着在雲服務器上面使用 git clone
現在的速度就很快了:
花三十秒的時間下載完畢。
cd awd-platform/
目錄中有AWD線下環境手冊文檔,但是在搭建的時候還是會有很多不完善的地方,綜合網上的多篇博客共同搭建並優化。
AWD環境中的機器按照功能分為幾種類型:
-
Check_Server:
服務檢查服務器,用於判定選手維護的服務是否可用,如果不可用,則會扣除相應的分數,不開啟任何端口,需要與flag服務器通信
簡單來說這台機器的作用就是檢查靶機宕機沒有
-
Flag_Server:
選手提交flag的服務器,並存儲選手的分數,開啟80端口
簡單來說這台機器就是獲取到flag后的提交對象,用於加分
-
Web_Server:
選手連接的服務器,選手需要對其進行維護,並嘗試攻擊其他隊伍的機器,通常開啟80端口,22端口,並將端口映射到主機。
這個就是我們每個隊伍所要操作的機器。
比賽邏輯拓補:
雲服務器首先安裝docker,有很多師傅寫過安裝docker的文章,跳過這一步。
接着下載鏡像
docker pull zhl2008/web_14.04
接着按照參考文章里面的:
所以我們將鏡像的名字修改:
docker tag zhl2008/web_14.04 web_14.04
接着我們按照文檔里面來進行操作:
后面的數字是靶機的數量,也就是參賽隊伍的數量,我們先復制所有隊伍的比賽文件夾和啟動比賽(這里啟動的是2個參賽隊):
python batch.py web_yunnan_simple 2 python start.py ./ 2
這里使用的靶機是 web_yunnan_simple ,至此,靶機就已經可以訪問了,因為是在一個服務器上運行了多個docker,靶機的端口映射規則為:
team1 ---- 8801
team3 ---- 8802
team3 ---- 8803
....以此類推
如圖:
各個靶機的ssh密碼在目錄文件夾下的pass.txt文件中,如圖
其ssh端口映射規則為:
team1 ---- 2201
team2 ---- 2202
team3 ---- 2203
....以此類推
但是我們還沒有啟動 check 腳本,而項目中原來的check腳本是不能用的,我們需要進行一些修改,這個規則要根據自己的環鏡自己編寫,總體思路就是判斷頁面是否存在,存在就加一分,不存在就減一分
網上有修改過后的 check 腳本,同時可以看到 flag-server 和 check-server 所映射的端口
使用大佬們的check.py腳本
#!/usr/bin/env python # -*- coding:utf8 -*- ''' ''' import hashlib import base64 sleep_time = 300 debug = True headers = {"User-Agent":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36"} import time import httplib import urllib2 import ssl my_time = 'AAAA' __doc__ = 'http(method,host,port,url,data,headers)' flag_server = '172.17.0.1' key = '744def038f39652db118a68ab34895dc' hosts = open('host.lists','r').readlines() user_id = [host.split(':')[0] for host in hosts] hosts = [host.split(':')[1] for host in hosts] port = 80 def http(method,host,port,url,data,headers): con=httplib.HTTPConnection(host,port,timeout=2) if method=='post' or method=='POST': headers['Content-Length']=len(data) headers['Content-Type']='application/x-www-form-urlencoded' con.request("POST",url,data,headers=headers) else: headers['Content-Length'] = 0 con.request("GET",url,headers=headers) res = con.getresponse() if res.getheader('set-cookie'): #headers['Cookie'] = res.getheader('set-cookie') pass if res.getheader('Location'): print "Your 302 direct is: "+res.getheader('Location') a = res.read() con.close() return a def https(method,host,port,url,data,headers): url = 'https://' + host + ":" + str(port) + url req = urllib2.Request(url,data,headers) response = urllib2.urlopen(req) return response.read() def get_score(): res = http('get',flag_server,8080,'/score.php?key=%s'%key,'',headers) print res user_scores = res.split('|') print "******************************************************************" res = '' print res print "******************************************************************" return user_scores def write_score(scores): scores = '|'.join(scores) res = http('get',flag_server,8080,'/score.php?key=%s&write=1&score=%s'%(key,scores),'',headers) if res == "success": return True else: print res raise ValueError class check(): def index_check(self): res = http('get',host,port,'/index.php?file=%s'%str(my_time),'',headers) if 'perspi' in res: return True if debug: print "[fail!] index_fail" return False def server_check(): try: a = check() if not a.index_check(): return False return True except Exception,e: print e return False game_round = 0 while True: scores = get_score() scores = [] print "--------------------------- round %d -------------------------------"%game_round for host in hosts: print "---------------------------------------------------------------" host = host[:-1] if server_check(): print "Host: "+host+" seems ok" scores.append("0") else: print "Host: "+host+" seems down" scores.append("-10") game_round += 1 write_score(scores) time.sleep(sleep_time)
按照文檔啟動check服務
docker attach check_server/
python check.py
使用的騰訊雲服務器,這一步的時候出錯了
連接超時,查看 check.py 源代碼
game_round = 0 while True: scores = get_score() scores = [] print "--------------------------- round %d -------------------------------"%game_round for host in hosts: print "---------------------------------------------------------------" host = host[:-1] if server_check(): print "Host: "+host+" seems ok" scores.append("0") else: print "Host: "+host+" seems down" scores.append("-10") game_round += 1 write_score(scores) time.sleep(sleep_time)
運行的是這一段
while循環調用,一段時間延遲后進行服務可用性的檢查,延遲時間由sleep_time決定
get_score() 函數報錯,查看報錯行:
res = http('get',flag_server,8080,'/score.php?key=%s'%key,'',headers)
調用了自寫頁面請求函數 http
而 flag_server 為全局變量:
my_time = 'AAAA' __doc__ = 'http(method,host,port,url,data,headers)' flag_server = '172.17.0.1' key = '744def038f39652db118a68ab34895dc' hosts = open('host.lists','r').readlines() user_id = [host.split(':')[0] for host in hosts] hosts = [host.split(':')[1] for host in hosts] port = 80
為:
flag_server = '172.17.0.1'
其靶機IP在host.lists文件中,ssh鏈接,查看其中一台靶機的IP
可以看到雲服務器上靶機的內網IP實際上為 172.18.0.2,所以才會報錯超時。
修改 flag_server
修改host.lists文件
重新啟動並進入容器:
可以看到現在 check已經正常了,但是host.lists文件是自動生成的,為了避免每次都修改,我們需要修改其自動化生成的腳本
簡單尋找了一下,初始化文件在start.py里面
如圖,host.lists文件寫入的IP根據我們的情況修改為 172.18.0
使用
python stop_clean.py
命令清理環境重新啟動服務,查看是否正常啟動
python batch.py web_yunnan_simple 3//復制3個web_yunnan_simple的靶機,數值可改 python start.py ./ 3 //啟動三個docker靶機和check服務器、flag_server服務器。數值可改 docker attach check_server //鏈接裁判機,檢查是否正常 python check.py
現在check裁判機就正常了
此外需要注意的是:
檢測頁面是否存活是在check.py中的 check類中,對於不同的環境,我們需要編寫不同的類方法來進行檢測服務是否宕機
該AWD平台另一個問題是可以無限交flag,即一個flag可以無限刷分,詳情和修改方法參考:
https://www.cnblogs.com/Triangle-security/p/11332223.html#
個人感覺修改方法不是很優雅hhh,因為需要自己提前去運行該腳本,這段時間有空想想其他的解決辦法,腳本如下:
#!/usr/bin/env python #coding:UTF-8 import time import os print int(time.time()) Unix_time = int(time.time()) print time.ctime(Unix_time) while True: time_his = [] time_list = ["00","05","10","15","20","25","30"] for i in time_list: dt = "2019-04-28 10:"+str(i)+":00" time_his.append(dt) a = time_his[0] b = time_his[1] c = time_his[2] d = time_his[3] e = time_his[4] f = time_his[5] g = time_his[6] time_stamp = [a,b,c,d,e,f,g] for k in time_stamp: h = open("time.txt", 'w+') timeArray = time.strptime(k, "%Y-%m-%d %H:%M:%S") timestamp = time.mktime(timeArray) print (int(timestamp)) data = (int(timestamp)) separated = '|' zero = '0' print >>h,(zero),(separated),(data),(separated),(zero),(separated),(data),(separated),(zero),(separated),(zero),(separated),(data),(separated),(zero),(separated),(zero), # 0|data|0|data|0|0|data|0|0 h.close() time.sleep(300)
另外,計分板在 IP:8080/score.txt中,界面不是很好看
使用夜莫離大佬的界面修改之
https://pan.baidu.com/s/18KlIeluaTtm-kT3KuXHseQ
密碼:cvdn
修改過程為:
計分板文件拷貝至awd-platform下的flag_server文件夾下。要注意將文件score.txt與result.txt文件權限調至777,這樣才能刷新出分值。
另外部分博客說的是修改 scorecard.php文件,下載上面的百度網盤文件后,發現其文件內容為:
應該是夜莫離后面又將scorecard.php改為了index.php,所以我們修改index.php中的IP地址
想要查看各隊得分情況直接訪問 IP:8080即可
可以看到因為只有三個隊伍,所以這里只有前三個隊伍有分數,為0,其余三個隊伍是沒有分數的,不知道如果開啟了超過六個隊伍,分數板會變成什么樣子。
至此AWD平台安裝完成。
該AWD平台題目環境較多,雖然安裝的時候問題比較多,但都是能夠克服的,盡管有無限刷分的bug,但是瑕不掩瑜,應該是開源AWD平台中最好的一個(很多沒有bug的平台,題目環境又太少了)。
自己也想寫一個AWD平台hhh,希望能夠自帶十道以上題目環境,一鍵部署,同時少量bug不影響正常使用,支持NPC隊伍,以及有代碼功底的使用者,能夠自己快速添加CMS題目環境進來,擴展題目種類。這樣就能夠方便很多因為各種原因不能進入線下的學校來進行AWD的練習了。
做技術的hack心態加上開放共進的態度是成長和越過高山幽谷的秘籍
以上
參考鏈接:
https://www.cnblogs.com/Triangle-security/p/11332223.html#
https://www.heibai.org/post/1468.html
https://blog.csdn.net/huanghelouzi/article/details/90204325