一、 AWD模式簡介
AWD:Attack With Defence,比賽中每個隊伍維護多台服務器,服務器中存在多個漏洞,利用漏洞攻擊其他隊伍可以進行得分,修復漏洞可以避免被其他隊伍攻擊失分。
- 一般分配Web服務器,服務器(多數為Linux)某處存在flag(一般在根目錄下);
- 可能會提供一台流量分析虛擬機,可以下載流量文件進行數據分析;
- flag在主辦方的設定下每隔一定時間刷新一輪;
- 各隊一般都有自己的初始分數;
- flag一旦被其他隊伍拿走,該隊扣除一定積分;
- 扣除的積分由獲取flag的隊伍均分;
- 主辦方會對每個隊伍的服務進行check,服務宕機扣除本輪flag分數,扣除的分值由服務check正常的隊伍均分;
- 一般每個隊伍會給一個低權限用戶,非root權限;
二、 網絡環境
網絡拓撲如下圖所示:
比賽中獲取flag一般有兩種模式:
(1)flag在根目錄下,讀取flag內容,提交即可得分
(2)拿到其他隊伍shell后,執行指定命令(curl 10.0.0.2),即可從上圖中flag機獲取flag內容;
比賽可能會告訴你其他隊伍的IP,也可能不會告訴你,一般在同一個C段或者B段,因此首先可以利用nmap等掃描工具發現其他隊伍的IP:
nmap –sn 192.168.71.0/24
或者用https://github.com/zer0h/httpscan 的腳本進行掃描
三、 比賽分工
線下賽一般3人左右,2人攻擊,1人防御,因為發現的漏洞可以攻擊其他隊伍,也要進行修復,所以攻防相輔相成,以攻為守。
比賽中每個隊伍可能會維護多個靶機,web、二進制等,也可以每人負責一台,各自負責攻擊和防御。
四、 一些“套路”
- 備份!備份!備份!
重要的事情說三遍,比賽開始后第一時間備份服務器中web目錄下的文件(/var/www/html),這是自我審計的基礎,也是防止服務器在比賽中出現異常的情況下可以立即恢復到初始狀態的先決條件。有的比賽可以提供3次左右的恢復初始設置的機會,有的比賽不提供,所以備份十分重要。
可以用scp命令,也可用一些圖形化的工具:Winscp,FileZilla,操作起來比較方便。
- 口令問題
弱口令的問題幾乎是必考,比賽開始后,如果發現每個隊伍的SSH賬號密碼都是一樣的(某次比賽中都是phpcms、wordpress),需要立即修改口令,如果被其他隊伍改了那就gg了。
Web后台很有可能存在弱口令,一般都是admin/admin,admin/123456,test/test等等,同樣需要立即修改,也可以修改其他隊伍的后台口令,為本隊所用,說不定可以利用后台getshell,比如十分常見的wordpress。
不過有的比賽不允許修改后台口令,如果修改視為服務宕機,這樣還是不要動口令的心思了。
- 預留后門
在維護的服務器上,很有可能已經預留了一個或多個后門,比如一句話木馬,這個是送分題,可以利用這個漏洞迅速打一波,還可以視情況“攪屎”,利用這個漏洞一直維持權限,每輪都得分(后面細說)
將服務器中web目錄下載到本地,利用D盾掃描,一般就可以發現預留后門:
發現后門后,第一時間刪除,同時利用這個漏洞發起第一波攻擊,如果利用菜刀連,顯然不夠優雅,還沒連完,人家估計都刪的差不多了,因此這個漏洞雖然是送分,但拼的是手速,因此得提前准備好腳本:
配置一下其他隊伍地址、shell路徑和密碼,就可以進行攻擊,flag記錄在firstround_flag.txt中,某次比賽實際情況如下:
- 常見漏洞
常見的漏洞包括SQL注入、文件包含、文件上傳等等。對於SQL注入類的漏洞,一般不會有過濾,可以用sqlmap
跑出來,再利用—sql-shell
執行select load_file(‘/flag’);
即可得到flag,也可以利用into outfile
寫木馬維持權限,但要根據實際情況,可能會遇到權限問題。用sqlmap跑比較耗時,可以利用payload寫一個python,自動化進行攻擊:
對於文件包含漏洞,直接可以通過../../../../../../flag
的方式獲取:
上傳漏洞一般也是比較簡單的黑名單過濾、服務器解析漏洞等等,可以直接上傳木馬;
五、 權限維持
這里說的方法就比較“攪屎”了,上面說到利用預留后門可以維持權限,主要有兩種,一種是“不死馬”,另一種是反彈shell
- “不死馬”
利用預留后門,上傳上面的“不死馬”並訪問,就會一直生成.config.php的一句話木馬,木馬內容可以自行修改,只要別被其他隊伍看懂就行。
這個不死馬比較猥瑣,解決的方法需要重啟apache,或者寫一個程序不停kill這個不死馬進程。
- 反彈shell
利用預留后門上傳上面的php文件並訪問,就可以用nc反彈shell,之后就可以一直得分了
需要注意的是,上面的2種方法,需要網站的權限為www-data,如果網站的權限是ctf,那么是沒有權限上傳文件的。
六、 通用防御
對於防御,一般通用有兩種方法:WAF、文件監控
(1)WAF
使用方法:
1.將waf.php傳到要包含的文件的目錄
2.在頁面中加入防護,有兩種做法,根據情況二選一即可:
a).在所需要防護的頁面加入代碼
require_once('waf.php');
就可以做到頁面防注入、跨站
如果想整站防注,就在網站的一個公用文件中,如數據庫鏈接文件config.inc.php中!
添加require_once('waf.php');
來調用本代碼
常用php系統添加文件
PHPCMS V9 \phpcms\base.php
PHPWIND8.7 \data\sql_config.php
DEDECMS5.7 \data\common.inc.php
DiscuzX2 \config\config_global.php
Wordpress \wp-config.php
Metinfo \include\head.php
b).在每個文件最前加上代碼
在php.ini中找到:
Automatically add files before or after any PHP document.
auto_prepend_file = 360_safe3.php路徑;
需要注意的是,部署waf可能會導致服務不可用,需要謹慎部署。
(2)文件監控
文件監控可以對web目錄進行監控,發現新上傳文件或者文件被修改立即恢復,這樣可以防止上傳shell等攻擊:
# -*- coding: utf-8 -*-
#use: python file_check.py ./
import os
import hashlib
import shutil
import ntpath
import time
CWD = os.getcwd()
FILE_MD5_DICT = {} # 文件MD5字典
ORIGIN_FILE_LIST = []
# 特殊文件路徑字符串
Special_path_str = 'drops_JWI96TY7ZKNMQPDRUOSG0FLH41A3C5EXVB82'
bakstring = 'bak_EAR1IBM0JT9HZ75WU4Y3Q8KLPCX26NDFOGVS'
logstring = 'log_WMY4RVTLAJFB28960SC3KZX7EUP1IHOQN5GD'
webshellstring = 'webshell_WMY4RVTLAJFB28960SC3KZX7EUP1IHOQN5GD'
difffile = 'diff_UMTGPJO17F82K35Z0LEDA6QB9WH4IYRXVSCN'
Special_string = 'drops_log' # 免死金牌
UNICODE_ENCODING = "utf-8"
INVALID_UNICODE_CHAR_FORMAT = r"\?%02x"
# 文件路徑字典
spec_base_path = os.path.realpath(os.path.join(CWD, Special_path_str))
Special_path = {
'bak' : os.path.realpath(os.path.join(spec_base_path, bakstring)),
'log' : os.path.realpath(os.path.join(spec_base_path, logstring)),
'webshell' : os.path.realpath(os.path.join(spec_base_path, webshellstring)),
'difffile' : os.path.realpath(os.path.join(spec_base_path, difffile)),
}
def isListLike(value):
return isinstance(value, (list, tuple, set))
# 獲取Unicode編碼
def getUnicode(value, encoding=None, noneToNull=False):
if noneToNull and value is None:
return NULL
if isListLike(value):
value = list(getUnicode(_, encoding, noneToNull) for _ in value)
return value
if isinstance(value, unicode):
return value
elif isinstance(value, basestring):
while True:
try:
return unicode(value, encoding or UNICODE_ENCODING)
except UnicodeDecodeError, ex:
try:
return unicode(value, UNICODE_ENCODING)
except:
value = value[:ex.start] + "".join(INVALID_UNICODE_CHAR_FORMAT % ord(_) for _ in value[ex.start:ex.end]) + value[ex.end:]
else:
try:
return unicode(value)
except UnicodeDecodeError:
return unicode(str(value), errors="ignore")
# 目錄創建
def mkdir_p(path):
import errno
try:
os.makedirs(path)
except OSError as exc:
if exc.errno == errno.EEXIST and os.path.isdir(path):
pass
else: raise
# 獲取當前所有文件路徑
def getfilelist(cwd):
filelist = []
for root,subdirs, files in os.walk(cwd):
for filepath in files:
originalfile = os.path.join(root, filepath)
if Special_path_str not in originalfile:
filelist.append(originalfile)
return filelist
# 計算機文件MD5值
def calcMD5(filepath):
try:
with open(filepath,'rb') as f:
md5obj = hashlib.md5()
md5obj.update(f.read())
hash = md5obj.hexdigest()
return hash
except Exception, e:
print u'[!] getmd5_error : ' + getUnicode(filepath)
print getUnicode(e)
try:
ORIGIN_FILE_LIST.remove(filepath)
FILE_MD5_DICT.pop(filepath, None)
except KeyError, e:
pass
# 獲取所有文件MD5
def getfilemd5dict(filelist = []):
filemd5dict = {}
for ori_file in filelist:
if Special_path_str not in ori_file:
md5 = calcMD5(os.path.realpath(ori_file))
if md5:
filemd5dict[ori_file] = md5
return filemd5dict
# 備份所有文件
def backup_file(filelist=[]):
# if len(os.listdir(Special_path['bak'])) == 0:
for filepath in filelist:
if Special_path_str not in filepath:
shutil.copy2(filepath, Special_path['bak'])
if __name__ == '__main__':
print u'---------start------------'
for value in Special_path:
mkdir_p(Special_path[value])
# 獲取所有文件路徑,並獲取所有文件的MD5,同時備份所有文件
ORIGIN_FILE_LIST = getfilelist(CWD)
FILE_MD5_DICT = getfilemd5dict(ORIGIN_FILE_LIST)
backup_file(ORIGIN_FILE_LIST) # TODO 備份文件可能會產生重名BUG
print u'[*] pre work end!'
while True:
file_list = getfilelist(CWD)
# 移除新上傳文件
diff_file_list = list(set(file_list) ^ set(ORIGIN_FILE_LIST))
if len(diff_file_list) != 0:
# import pdb;pdb.set_trace()
for filepath in diff_file_list:
try:
f = open(filepath, 'r').read()
except Exception, e:
break
if Special_string not in f:
try:
print u'[*] webshell find : ' + getUnicode(filepath)
shutil.move(filepath, os.path.join(Special_path['webshell'], ntpath.basename(filepath) + '.txt'))
except Exception as e:
print u'[!] move webshell error, "%s" maybe is webshell.'%getUnicode(filepath)
try:
f = open(os.path.join(Special_path['log'], 'log.txt'), 'a')
f.write('newfile: ' + getUnicode(filepath) + ' : ' + str(time.ctime()) + '\n')
f.close()
except Exception as e:
print u'[-] log error : file move error: ' + getUnicode(e)
# 防止任意文件被修改,還原被修改文件
md5_dict = getfilemd5dict(ORIGIN_FILE_LIST)
for filekey in md5_dict:
if md5_dict[filekey] != FILE_MD5_DICT[filekey]:
try:
f = open(filekey, 'r').read()
except Exception, e:
break
if Special_string not in f:
try:
print u'[*] file had be change : ' + getUnicode(filekey)
shutil.move(filekey, os.path.join(Special_path['difffile'], ntpath.basename(filekey) + '.txt'))
shutil.move(os.path.join(Special_path['bak'], ntpath.basename(filekey)), filekey)
except Exception as e:
print u'[!] move webshell error, "%s" maybe is webshell.'%getUnicode(filekey)
try:
f = open(os.path.join(Special_path['log'], 'log.txt'), 'a')
f.write('diff_file: ' + getUnicode(filekey) + ' : ' + getUnicode(time.ctime()) + '\n')
f.close()
except Exception as e:
print u'[-] log error : done_diff: ' + getUnicode(filekey)
pass
time.sleep(2)
# print '[*] ' + getUnicode(time.ctime())
七、 自動提交
有的比賽只有幾分鍾一輪,手工提交其他隊伍flag顯然不行,需要准備批量提交flag的腳本:
八、 流量、日志
通過流量、日志的分析:
1.感知可能正在發生的攻擊,從而規避存在的安全風險
2.應急響應,還原攻擊者的攻擊路徑,從而挽回已經造成的損失
轉自:https://xz.aliyun.com/t/25