巡風的掃描與漏斗檢測腳本分析


啟動並在配置好之后,巡風就i在后端開始的資產探測的掃描,先來看一下需要啟動的三個腳本:aider.py、nasscan.py和vulscan.py

0x01:aider.py

這個腳本主要作用有兩個,一是用作dns,建立socket連接,一個簡單的DNS log平台,啟動兩個線程,一個線程執行udp服務,一個執行http服務;二是用來判斷無返回類型的服務

import socket,thread,datetime,time
query_history = []
url_history = []

def web_server():
    # 創建http服務
    web = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    # 監聽8088(http)端口
    web.bind(('0.0.0.0',8088))
    # 監聽端口
    web.listen(10)

    while True:
        try:
            #     被動接受TCP客戶端連接,(阻塞式)等待連接的到來
            # 連接成功返回非負值,失敗時返回-1
            conn,addr = web.accept()
            # recv接受tcpp數據,最大為4096字節
            data = conn.recv(4096)
            req_line = data.split("\r\n")[0]
            path = req_line.split()[1]
            route_list = path.split('/')
            html = "NO"
            if len(route_list) == 3:
                if route_list[1] == 'add':
                    if route_list[2] not in url_history:
                        url_history.append(route_list[2])
                elif route_list[1] == 'check':
                    if route_list[2] in url_history:
                        url_history.remove(route_list[2])
                        html = 'YES'
            else:
                query_str = route_list[1]
                for query_raw in query_history:
                    if query_str in query_raw:
                        query_history.remove(query_raw)
                        html = "YES"
            print datetime.datetime.now().strftime('%m-%d %H:%M:%S') + " " + str(addr[0]) +' web query: ' + path
            raw = "HTTP/1.0 200 OK\r\nContent-Type: application/json; charset=utf-8\r\nContent-Length: %d\r\nConnection: close\r\n\r\n%s" %(len(html),html)
            conn.send(raw)
            conn.close()
        except:
            pass


if __name__=="__main__":
    # 創建一個socket對象,規定套接字家族和類型(非面向連接)
    dns = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    # 監聽53(udp)端口
    dns.bind(('0.0.0.0', 53))
    # start_new_thread創建一個新線程,返回線程標識符
    thread.start_new_thread(web_server,())
    while True:
        try:
            # 接受udp數據,但返回值是(data,address)。其中data是包含接收數據的字符串,address是發送數據的套接字地址。
            recv,addr = dns.recvfrom(1024)
            # 將請求添加到query_history數組中。
            if recv not in query_history:query_history.append(recv)
            print datetime.datetime.now().strftime('%m-%d %H:%M:%S') + " " +str(addr[0]) +' Dns Query: ' + recv
        except Exception,e:
            print e
aider.py

 

0x02:nasscan.py

大致的順一下這個腳本的功能順序:獲取前端配置-進行日志記錄-讀取統計信息-判斷是否應該掃描(包括兩種情況)-刪除失效記錄-開始掃描

這個腳本主要是用來進行網絡資產的掃描,包括探測存活主機、開放端口、服務等。

if __name__ == "__main__":
    try:
        # 讀取配置
        CONFIG_INI = get_config()
        # 日志記錄,傳入參數scan_type, host, port, info
        log.write('info', None, 0, u'獲取配置成功')
        # 讀取統計信息
        STATISTICS = get_statistics()
        print STATISTICS
        MASSCAN_AC = [0]    # 標識符 masscan是否在使用
        NACHANGE = [0]      # 標識符 掃描列表是否被改變
        thread.start_new_thread(monitor, (CONFIG_INI, STATISTICS, NACHANGE))  # 心跳線程,主要用於判斷掃描配置是否發生了變化
        thread.start_new_thread(cruise, (STATISTICS, MASSCAN_AC))  # 失效記錄刪除線程
        socket.setdefaulttimeout(int(CONFIG_INI['Timeout']) / 2)  # 設置連接超時
        ac_data = []
        # 掃描循環
        while True:
            # 獲取當前具體時間信息
            now_time = time.localtime()
            # 獲取當前小時
            now_hour = now_time.tm_hour
            # 獲取當前星期幾
            now_day = now_time.tm_mday
            # 獲取年月日
            now_date = str(now_time.tm_year) + str(now_time.tm_mon) + str(now_day)
            # 獲取資產探測周期
            cy_day, ac_hour = CONFIG_INI['Cycle'].split('|')
            log.write('info', None, 0, u'掃描規則: ' + str(CONFIG_INI['Cycle']))
            # 判斷是否進入掃描時段
            # 判斷是否達到了一個掃描的周期,或者心跳線程是否檢測到掃描列表更新
            # 在心跳線程中可以看到base64不同時會將NACHANGE[0]置於1
            if (now_hour == int(ac_hour) and now_day % int(cy_day) == 0 and now_date not in ac_data) or NACHANGE[0]:
                # 判斷是否掃描過列表
                ac_data.append(now_date)
                NACHANGE[0] = 0
                log.write('info', None, 0, u'開始掃描')
                # 聲明一個start對象,並傳入配置參數
                s = start(CONFIG_INI)
                # 標識masscan是否在使用,標識掃描列表是否被改變
                s.masscan_ac = MASSCAN_AC
                s.statistics = STATISTICS
                # 開始掃描
                s.run()
            time.sleep(60)
    except Exception, e:
        print e
View Code

 

這個方法用來獲取配置頁面中的各項配置

def get_config():
    config = {}
    # 從mongodb中讀取`nascan`的配置,可以看到Config集合中有`vulscan`和`nascan`的掃描配置
    config_info = mongo.na_db.Config.find_one({"type": "nascan"})
    for name in config_info['config']:
        # 對於cms識別、組件容器、動態語言、服務 的配置存儲是使用`|`進行分割存儲的
        # 所以在取出之前要進行簡單的格式化然后放到配置中
        if name in ['Discern_cms', 'Discern_con', 'Discern_lang', 'Discern_server']:
            config[name] = format_config(name, config_info['config'][name]['value'])
        else:
            config[name] = config_info['config'][name]['value']
    return config
get_config

 

用來進行日志記錄

import threading
import time
import sys
reload(sys)
sys.setdefaultencoding('utf8')
# 線程互斥鎖
mutex = threading.Lock()
def write(scan_type, host, port, info):
    # 上鎖,避免多個進程輸出,導致格式混亂
    mutex.acquire()
    port = int(port)
    try:
        time_str = time.strftime('%X', time.localtime(time.time()))
        # 根據傳入的scan_type,判斷輸出內容
        if scan_type == 'portscan':
            print "[%s] %s:%d open" % (time_str, host, port)
        elif scan_type == 'server':
            print "[%s] %s:%d is %s" % (time_str, host, port, str(info))
        elif scan_type == 'web':
            print "[%s] %s:%d is web" % (time_str, host, port)
            print "[%s] %s:%d web info %s" % (time_str, host, port, info)
        elif scan_type == 'active':
            print "[%s] %s active" % (time_str, host)
        elif scan_type == 'info':
            print "[%s] %s" % (time_str, info)
    except Exception, e:
        print 'logerror',e
        pass
    # 釋放鎖
    mutex.release()
log.write

 

讀取統計信息

def get_statistics():
    # 獲取當日的統計信息
    date_ = datetime.datetime.now().strftime('%Y-%m-%d')
    now_stati = mongo.na_db.Statistics.find_one({"date": date_})
    if not now_stati:
        # 沒有當日的信息則返回一個初始統計信息
        now_stati = {date_: {"add": 0, "update": 0, "delete": 0}}
        return now_stati
    else:
        # 有則返回
        return {date_: now_stati['info']}
get_statistics

 

心跳線程,判斷是否到達掃描周期或資產探測列表已改變

# 心跳線程,主要用於判斷掃描配置是否發生了變化,如果改變,則立即觸發掃描
def monitor(CONFIG_INI, STATISTICS, NACHANGE):
    while True:
        try:
            time_ = datetime.datetime.now()
            # 記錄心跳
            date_ = time_.strftime('%Y-%m-%d')
            mongo.na_db.Heartbeat.update({"name": "heartbeat"}, {"$set": {"up_time": time_}})
            if date_ not in STATISTICS: STATISTICS[date_] = {"add": 0, "update": 0, "delete": 0}
            # 更新統計信息
            mongo.na_db.Statistics.update({"date": date_}, {"$set": {"info": STATISTICS[date_]}}, upsert=True)
            new_config = get_config()   # 獲取最新配置
            # 比較配置掃描列表的base64是否相同,不同則置NACHANGE[0]為1,說明配置發生改名,立即出發掃描
            if base64.b64encode(CONFIG_INI["Scan_list"]) != base64.b64encode(new_config["Scan_list"]):NACHANGE[0] = 1
            CONFIG_INI.clear()
            # 更新新配置
            CONFIG_INI.update(new_config)
        except Exception, e:
            print e
        # 每30秒檢測一次
        time.sleep(30)
monitor

 

失效記錄刪除線程

#   失效記錄,刪除線程,STATISTICS統計信息,MASSCAN_AC判斷是否啟用masscan
def cruise(STATISTICS,MASSCAN_AC):
    while True:
        # 獲取當前日期(年 月 日 時 分 秒)
        now_str = datetime.datetime.now()
        # 獲取當天是星期幾0-6
        week = int(now_str.weekday())
        # 獲取當前時間的整點數
        hour = int(now_str.hour)
        if week >= 1 and week <= 5 and hour >= 9 and hour <= 18:  # 非工作時間不刪除
            try:
                # 獲取掃描信息記錄,根據time字段進行升序排列
                data = mongo.NA_INFO.find().sort("time", 1)
                for history_info in data:
                    while True:
                        # 如果masscan正在掃描即不進行清理,在用masscan進行掃描的時候會置1
                        if MASSCAN_AC[0]:  # 如果masscan正在掃描即不進行清理
                            time.sleep(10)
                        else:
                            break
                    ip = history_info['ip']
                    port = history_info['port']
                    try:
                        # 檢測端口是否存活
                        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                        sock.connect((ip, int(port)))
                        sock.close()
                    except Exception, e:
                        time_ = datetime.datetime.now()
                        date_ = time_.strftime('%Y-%m-%d')
                        # 不存活則刪除改記錄
                        mongo.NA_INFO.remove({"ip": ip, "port": port})
                        # 日志記錄
                        log.write('info', None, 0, '%s:%s delete' % (ip, port))
                        STATISTICS[date_]['delete'] += 1
                        del history_info["_id"]
                        history_info['del_time'] = time_
                        history_info['type'] = 'delete'
                        # 添加一條操作歷史
                        mongo.NA_HISTORY.insert(history_info)
            except:
                pass
        # 每小時檢測一次
        time.sleep(3600)
cruise

 

start類,掃描的主要方法,包括IP地址段的解析,端口的掃描,白名單的繞過等

class start:
    def __init__(self, config):  # 默認配置
        # 傳入CONFIG_INI 配置,然后設置類的屬性
        self.config_ini = config
        self.queue = Queue.Queue()  # 隊列對象
        self.thread = int(self.config_ini['Thread'])  # 最大線程數
        self.scan_list = self.config_ini['Scan_list'].split('\n')   # 掃描列表
        self.mode = int(self.config_ini['Masscan'].split('|')[0])   # MASSCAN配置
        self.icmp = int(self.config_ini['Port_list'].split('|')[0]) # 端口列表
        self.white_list = self.config_ini.get('White_list', '').split('\n') # 白名單

    # 啟動函數
    def run(self):
        # 在start.py中定義的全局變量,端口列表
        global AC_PORT_LIST
        all_ip_list = []
        for ip in self.scan_list:
            # 處理CIDR格式的ip, eg:192.168.0.1/24
            # 就不具體跟進看了,大約40行左右,涉及一些位運算格式轉換啥的
            if "/" in ip:
                # 把CIDR格式的地址轉換成地址段
                ip = cidr.CIDR(ip)
            if not ip:
                continue
            # 獲取IP列表
            ip_list = self.get_ip_list(ip)
            # 對於白名單ip進行移除
            for white_ip in self.white_list:
                if white_ip in ip_list:
                    ip_list.remove(white_ip)
            # 是否開始了masscan掃描,開啟了mode置為1,否則為0
            if self.mode == 1:
                # 獲取文件路徑
                masscan_path = self.config_ini['Masscan'].split('|')[2]
                # 獲取掃描速率
                masscan_rate = self.config_ini['Masscan'].split('|')[1]
                # 如果用戶在前台關閉了ICMP存活探測則進行全IP段掃描
                # 獲取存活IP
                if self.icmp:
                    ip_list = self.get_ac_ip(ip_list)
                self.masscan_ac[0] = 1
                # 如果安裝了Masscan即使用Masscan進行全端口掃描
                AC_PORT_LIST = self.masscan(
                    ip_list, masscan_path, masscan_rate)
                if not AC_PORT_LIST:
                    continue
                # 將self.masscan_ac[0]置0,表示結束使用
                self.masscan_ac[0] = 0
                for ip_str in AC_PORT_LIST.keys():
                    self.queue.put(ip_str)  # 加入隊列
                self.scan_start()  # 開始掃描
            else:
                all_ip_list.extend(ip_list)
        # 不使用masscan掃描
        if self.mode == 0:
            if self.icmp:
                all_ip_list = self.get_ac_ip(all_ip_list)
            for ip_str in all_ip_list:
                self.queue.put(ip_str)  # 加入隊列
            self.scan_start()  # TCP探測模式開始掃描

    # 開始掃描
    def scan_start(self):
        for i in range(self.thread):  # 開始掃描
            t = ThreadNum(self.queue)
            t.setDaemon(True)
            t.mode = self.mode
            t.config_ini = self.config_ini
            t.statistics = self.statistics
            t.start()
        self.queue.join()

    def masscan(self, ip, masscan_path, masscan_rate):
        try:
            if len(ip) == 0:
                return
            sys.path.append(sys.path[0] + "/plugin")
            # 導入masscan.py,並在下面調用它的run方法
            m_scan = __import__("masscan")
            result = m_scan.run(ip, masscan_path, masscan_rate)
            return result
        except Exception, e:
            print e
            print 'No masscan plugin detected'

    # 接受cidr格式的ip,返回IP列表
    def get_ip_list(self, ip):
        ip_list_tmp = []
        def iptonum(x): return sum([256 ** j * int(i)
                                    for j, i in enumerate(x.split('.')[::-1])])
        def numtoip(x): return '.'.join(
            [str(x / (256 ** i) % 256) for i in range(3, -1, -1)])
        if '-' in ip:
            ip_range = ip.split('-')
            ip_start = long(iptonum(ip_range[0]))
            ip_end = long(iptonum(ip_range[1]))
            ip_count = ip_end - ip_start
            if ip_count >= 0 and ip_count <= 655360:
                for ip_num in range(ip_start, ip_end + 1):
                    ip_list_tmp.append(numtoip(ip_num))
            else:
                print 'IP format error'
        else:
            ip_split = ip.split('.')
            net = len(ip_split)
            if net == 2:
                for b in range(1, 255):
                    for c in range(1, 255):
                        ip = "%s.%s.%d.%d" % (ip_split[0], ip_split[1], b, c)
                        ip_list_tmp.append(ip)
            elif net == 3:
                for c in range(1, 255):
                    ip = "%s.%s.%s.%d" % (
                        ip_split[0], ip_split[1], ip_split[2], c)
                    ip_list_tmp.append(ip)
            elif net == 4:
                ip_list_tmp.append(ip)
            else:
                print "IP format error"
        return ip_list_tmp

    # 通過ping請求來探測主機存活,后期只對存活主機進行掃描
    def get_ac_ip(self, ip_list):
        try:
            s = icmp.Nscan()
            ipPool = set(ip_list)
            return s.mPing(ipPool)
        except Exception, e:
            print 'The current user permissions unable to send icmp packets'
            return ip_list
start類

 

動態引入masscan.py腳本(m_scan = __import__("masscan"))

def run(ip_list,path,rate):
    try:
        ip_file = open('target.log','w')
        # 將存活的ip列表寫到target.log中
        ip_file.write("\n".join(ip_list))
        ip_file.close()
        # 進行過濾一些危險字符,translate用來轉換字符串字符
        path = str(path).translate(None, ';|&`\n')
        rate = str(rate).translate(None, ';|&`\n')
        if not os.path.exists(path):return
        # 用系統命令進行masscan全端口掃描
        os.system("%s -p1-65535 -iL target.log -oL tmp.log --randomize-hosts --rate=%s"%(path,rate))
        # 讀取掃描結果
        result_file = open('tmp.log', 'r')
        result_json = result_file.readlines()
        result_file.close()
        del result_json[0]
        del result_json[-1]
        open_list = {}
        # 對掃描結果進行格式化處理
        for res in result_json:
            try:
                ip = res.split()[3]
                port = res.split()[2]
                if ip in open_list:
                    open_list[ip].append(port)
                else:
                    open_list[ip] = [port]
            except:pass
        os.remove('target.log')
        os.remove('tmp.log')
        # 返回掃描結果
        return open_list
    except:
        pass
masscan.py run

 

0x03:vulscan.py

用於對掃出的資產進行漏洞掃描,具體的掃描過程依賴於vuldb中的插件形式進行掃描,

 

main函數:腳本運行的開始函數,主要是啟動心跳線程、漏斗檢查線程、更新kunpeng插件線程、獲取任務、清理插件緩存等。

if __name__ == '__main__':
    init()
    # 密碼字典、線程數量、超時時間、白名單
    PASSWORD_DIC, THREAD_COUNT, TIMEOUT, WHITE_LIST = get_config()
    # 開啟心跳線程
    thread.start_new_thread(monitor, ())
    # 開啟漏斗檢查線程
    thread.start_new_thread(kp_check, ())
    # 開啟更新kunpeng線程
    thread.start_new_thread(kp_update, ())
    while True:
        try:
            # 獲取未執行任務的任務id,、任務周期、任務目標、任務插件
            task_id, task_plan, task_target, task_plugin = queue_get()
            if task_id == '':
                time.sleep(10)
                continue
            # 清理插件緩存
            if PLUGIN_DB:
                del sys.modules[PLUGIN_DB.keys()[0]]
                PLUGIN_DB.clear()
            for task_netloc in task_target:
                while True:
                    # thread._count返回正在運行的線程數量,包括主線程
                    if int(thread._count()) < THREAD_COUNT:
                        # 繞過白名單
                        if task_netloc[0] in WHITE_LIST:
                            break
                        # 進行漏斗檢測
                        try:
                            thread.start_new_thread(
                                vulscan, (task_id, task_netloc, task_plugin))
                        except Exception as e:
                            print e
                        break
                    else:
                        time.sleep(2)
            # 更新status文檔
            if task_plan == 0:
                na_task.update({"_id": task_id}, {"$set": {"status": 2}})
        except Exception as e:
            print e
__main__

 

 

init函數:用來初始化,獲取插件列表,安裝kunpeng等。

def init():
    time_ = datetime.datetime.now()
    if na_plugin.find().count() >= 1:
        return
    script_plugin = []
    json_plugin = []
    print 'init plugins'
    # 獲取插件列表
    file_list = os.listdir(sys.path[0] + '/vuldb')
    # 把插件添加到對應的列表中
    for filename in file_list:
        try:
            if filename.split('.')[1] == 'py':
                script_plugin.append(filename.split('.')[0])
            if filename.split('.')[1] == 'json':
                json_plugin.append(filename)
        except:
            pass
    for plugin_name in script_plugin:
        try:
            # 動態加載
            res_tmp = __import__(plugin_name)
            # 獲取插件信息
            plugin_info = res_tmp.get_plugin_info()
            plugin_info['add_time'] = time_
            plugin_info['filename'] = plugin_name
            plugin_info['count'] = 0
            na_plugin.insert(plugin_info)
        except:
            pass
    for plugin_name in json_plugin:
        try:
            json_text = open(sys.path[0] + '/vuldb/' + plugin_name, 'r').read()
            plugin_info = json.loads(json_text)
            plugin_info['add_time'] = time_
            plugin_info['filename'] = plugin_name
            plugin_info['count'] = 0
            del plugin_info['plugin']
            na_plugin.insert(plugin_info)
        except:
            pass
    # 安裝kunpeng
    install_kunpeng_plugin()
init

 

 

get_config:從數據庫里查找配置,白名單、線程數、超時時間等

def get_config():
    try:
        # 在Config集合中,找到type=vulscan的文檔
        config_info = na_config.find_one({"type": "vulscan"})
        # 下面都是獲取這個文檔中的一些值,都是字典類型的
        pass_row = config_info['config']['Password_dic']
        thread_row = config_info['config']['Thread']
        timeout_row = config_info['config']['Timeout']
        white_row = config_info['config']['White_list']
        password_dic = pass_row['value'].split('\n')
        thread_count = int(thread_row['value'])
        timeout = int(timeout_row['value'])
        white_list = white_row['value'].split('\n')
        return password_dic, thread_count, timeout, white_list
    except Exception, e:
        print e
get_config

 

 

monitor:心跳線程函數,看的不是很明白,希望大佬們可以支教

def monitor():
    global PASSWORD_DIC, THREAD_COUNT, TIMEOUT, WHITE_LIST
    while True:
        # 從數據庫里找到相應的值
        queue_count = na_task.find({"status": 0, "plan": 0}).count()
        if queue_count:
            load = 1
        else:
            ac_count = thread._count()
            load = float(ac_count - 6) / THREAD_COUNT
        if load > 1:
            load = 1
        if load < 0:
            load = 0
        na_heart.update({"name": "load"}, {
                        "$set": {"value": load, "up_time": datetime.datetime.now()}})
        PASSWORD_DIC, THREAD_COUNT, TIMEOUT, WHITE_LIST = get_config()
        if load > 0:
            time.sleep(8)
        else:
            time.sleep(60)
monitor

 

 

kp_check:調用kunpeng,進行檢查

def kp_check():
    while True:
        try:
            new_release = kp.check_version()
            print new_release
            if new_release:
                info = new_release['body']
                if '###' in new_release['body']:
                    info = new_release['body'].split('###')[1]
                row = {
                    'info': info,
                    'isInstall': 0,
                    'name': new_release['name'],
                    'author': new_release['author']['login'],
                    'pushtime': new_release['published_at'],
                    'location': "",
                    'unicode': new_release['tag_name'],
                    'coverage': 0,
                    'source': 'kunpeng'
                }
                na_update.insert(row)
                time.sleep(60 * 60 * 48)
        except Exception as e:
            print e
        time.sleep(60 * 30)
kp_check

 

 

kp_update:更新kunpeng插件

def kp_update():
    while True:
        try:
            # 找到相應的文檔刪除,並返回刪除的數量
            row = na_update.find_one_and_delete(
                {'source': 'kunpeng', 'isInstall': 1})
            if row:
                kp.update_version(row['unicode'])
                na_plugin.delete_many({'_id':re.compile('^KP')})
                install_kunpeng_plugin()
        except Exception as e:
            print e
        time.sleep(10)
kp_update

 

 

queue_get:獲取任務信息,id、周期、插件等

def queue_get():
    # 全局字典
    global TASK_DATE_DIC
    # 在Task這個集合中提取並更新數據,找到所有status等於0,plan等於0的文檔,返回后更新status=1,然后根據時間升序排列
    # find_and_modify
    task_req = na_task.find_and_modify(query={"status": 0, "plan": 0}, update={
                                       "$set": {"status": 1}}, sort={'time': 1})
    # 返回未執行任務
    if task_req:
        TASK_DATE_DIC[str(task_req['_id'])] = datetime.datetime.now()
        return task_req['_id'], task_req['plan'], task_req['target'], task_req['plugin']
    # 不存在還未執行任務,返回空或返回定期執行任務
    else:
        # $ne表示不等於
        task_req_row = na_task.find({"plan": {"$ne": 0}})
        if task_req_row:
            # 執行定期任務
            for task_req in task_req_row:
                if (datetime.datetime.now() - task_req['time']).days / int(task_req['plan']) >= int(task_req['status']):
                    if task_req['isupdate'] == 1:
                        task_req['target'] = update_target(
                            json.loads(task_req['query']))
                        na_task.update({"_id": task_req['_id']}, {
                                       "$set": {"target": task_req['target']}})
                    na_task.update({"_id": task_req['_id']}, {
                                   "$inc": {"status": 1}})
                    TASK_DATE_DIC[str(task_req['_id'])
                                  ] = datetime.datetime.now()
                    return task_req['_id'], task_req['plan'], task_req['target'], task_req['plugin']
        return '', '', '', ''
queue_get

 

 

vulscan類:這是主要的,漏斗的檢測主要在這里面進行。

class vulscan():
    def __init__(self, task_id, task_netloc, task_plugin):
        self.task_id = task_id      # 任務id
        self.task_netloc = task_netloc  # 任務目標
        self.task_plugin = task_plugin # 任務插件
        self.result_info = ''
        self.start()

    # 線程啟動函數,與run不同,會在一個新線程里開啟
    def start(self):
        self.get_plugin_info()  # 獲取插件信息
        if '.json' in self.plugin_info['filename']:  # 標示符檢測模式
            self.load_json_plugin()  # 讀取漏洞標示
            self.set_request()  # 標示符轉換為請求
            self.poc_check()  # 檢測
        elif 'KP-' in self.plugin_info['filename']:
            self.log(str(self.task_netloc) + 'call kunpeng - ' + self.plugin_info['filename'])
            kp.set_config(TIMEOUT, PASSWORD_DIC)
            if self.task_netloc[1] != 80:
                self.result_info = kp.check('service', '{}:{}'.format(
                    self.task_netloc[0], self.task_netloc[1]), self.plugin_info['filename'])
            if not self.result_info:
                scheme = 'http'
                if self.task_netloc[1] == 443:
                    scheme = 'https'
                self.result_info = kp.check('web', '{}://{}:{}'.format(
                    scheme, self.task_netloc[0], self.task_netloc[1]), self.plugin_info['filename'])
        else:  # 腳本檢測模式
            plugin_filename = self.plugin_info['filename']
            self.log(str(self.task_netloc) + 'call ' + self.task_plugin)
            if task_plugin not in PLUGIN_DB: # 字典
                plugin_res = __import__(plugin_filename)
                setattr(plugin_res, "PASSWORD_DIC", PASSWORD_DIC)  # 給插件聲明密碼字典
                PLUGIN_DB[plugin_filename] = plugin_res
            self.result_info = PLUGIN_DB[plugin_filename].check(
                str(self.task_netloc[0]), int(self.task_netloc[1]), TIMEOUT)
        self.save_request()  # 保存結果

    # 獲取插件信息
    def get_plugin_info(self):
        # 從插件庫中找到插件
        info = na_plugin.find_one({"name": self.task_plugin})
        self.plugin_info = info

    def load_json_plugin(self):
        json_plugin = open(sys.path[0] + '/vuldb/' +
                           self.plugin_info['filename']).read()
        self.plugin_info['plugin'] = json.loads(json_plugin)['plugin']
    # 構造請求
    def set_request(self):
        url = 'http://' + \
            self.task_netloc[0] + ":" + \
            str(self.task_netloc[1]) + self.plugin_info['plugin']['url']
        if self.plugin_info['plugin']['method'] == 'GET':
            request = urllib2.Request(url)
        else:
            request = urllib2.Request(url, self.plugin_info['plugin']['data'])
        self.poc_request = request

    # 獲取代碼語言,主要是通過正則匹配
    def get_code(self, header, html):
        try:
            m = re.search(r'<meta.*?charset=(.*?)"(>| |/)', html, flags=re.I)
            if m:
                return m.group(1).replace('"', '')
        except:
            pass
        try:
            if 'Content-Type' in header:
                Content_Type = header['Content-Type']
                m = re.search(r'.*?charset=(.*?)(;|$)',
                              Content_Type, flags=re.I)
                if m:
                    return m.group(1)
        except:
            pass

    def poc_check(self):
        try:
            # 發送請求
            res = urllib2.urlopen(self.poc_request, timeout=30)
            res_html = res.read(204800)
            # 獲取i請求頭
            header = res.headers
            # res_code = res.code
        except urllib2.HTTPError, e:
            # res_code = e.code
            header = e.headers
            res_html = e.read(204800)
        except Exception, e:
            return
        try:
            # 獲取編碼語言
            html_code = self.get_code(header, res_html).strip()
            if html_code and len(html_code) < 12:
                res_html = res_html.decode(html_code).encode('utf-8')
        except:
            pass
        an_type = self.plugin_info['plugin']['analyzing']
        vul_tag = self.plugin_info['plugin']['tag']
        analyzingdata = self.plugin_info['plugin']['analyzingdata']
        if an_type == 'keyword':
            # print poc['analyzingdata'].encode("utf-8")
            if analyzingdata.encode("utf-8") in res_html:
                self.result_info = vul_tag
        elif an_type == 'regex':
            if re.search(analyzingdata, res_html, re.I):
                self.result_info = vul_tag
        elif an_type == 'md5':
            md5 = hashlib.md5()
            md5.update(res_html)
            if md5.hexdigest() == analyzingdata:
                self.result_info = vul_tag

    #
    def save_request(self):
        if self.result_info:
            time_ = datetime.datetime.now()
            self.log(str(self.task_netloc) + " " + self.result_info)
            v_count = na_result.find(
                {"ip": self.task_netloc[0], "port": self.task_netloc[1], "info": self.result_info}).count()
            if not v_count:
                na_plugin.update({"name": self.task_plugin},
                                 {"$inc": {'count': 1}})
            vulinfo = {"vul_name": self.plugin_info['name'], "vul_level": self.plugin_info['level'],
                       "vul_type": self.plugin_info['type']}
            w_vul = {"task_id": self.task_id, "ip": self.task_netloc[0], "port": self.task_netloc[1],
                     "vul_info": vulinfo, "info": self.result_info, "time": time_,
                     "task_date": TASK_DATE_DIC[str(self.task_id)]}
            na_result.insert(w_vul)
            # self.wx_send(w_vul)  # 自行定義漏洞提醒

    def log(self, info):
        lock.acquire()
        try:
            time_str = time.strftime('%X', time.localtime(time.time()))
            print "[%s] %s" % (time_str, info)
        except:
            pass
        lock.release()
vulscan類

 

 

參考文章:https://xz.aliyun.com/t/4104#toc-9

*************不積跬步無以至千里*************


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM