巡风的扫描与漏斗检测脚本分析

启动并在配置好之后,巡风就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

*************不积跬步无以至千里*************

posted @ 2019-11-01 17:12  梁十安  阅读(1277)  评论(0编辑  收藏  举报