開學后實驗室來了幾個新同學,在線上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
