巡風掃描器web界面工作流程


這兩周學習了巡風掃描器的搭建,也在學長的帶領下看了各部分的下源代碼,為了加深記憶,梳理一下巡風大體的工作流程,主要通過web端的頁面分析,錯誤的地方還請大佬們多多指正。

 

整體看一下巡風的掃描流程:登陸->配置頁面進行配置->到統計頁面查看記錄總數、IP總數、以及漏斗分析->到搜索頁面輸入搜索條件->選中一個或多個搜索結果,右上角新增任務->輸入任務名稱,選擇插件->點擊任務名稱,即可查看任務詳情

一:登陸頁面

 

 

 看一下后端的登陸函數,獲取前端輸入的用戶名、密碼,並通過app驗證,app在__init.py__這個腳本里被初始化,是連接數據庫的對象。

# 登錄
@app.route('/login', methods=['get', 'post'])
def Login():
    if request.method == 'GET':
        return render_template('login.html')
    else:
        # 獲取前端輸入的用戶名密碼
        account = request.form.get('account')
        password = request.form.get('password')
        if account == app.config.get('ACCOUNT') and password == app.config.get('PASSWORD'):
            session['login'] = 'loginsuccess'
            return redirect(url_for('Search'))
        else:
            return redirect(url_for('Login'))

 

二:配置頁面

登錄之后,點擊配置頁面,默認是爬蟲引擎配置,還有一種是掃描引擎配置,后端是通過get請求的參數區分,在配置頁面,可以自定義掃描方式、線程數、超時時間、資產列表等。更改配置后,后端通過UpdateConfig這個視圖函數更新配置,

 下面讓看下每一項配置的作用:

資產探測周期配置:

每天固定的時間點掃描,進行資產探測收集。

網絡資產探測列表配置

在這個地方,可以設置需要探測的內網的地址段,可以設置成圖中格式,也可以設置成cidr(https://www.cnblogs.com/liangxiyang/p/11628000.html)地址格式,另外,探測列表一旦更改,則會立即觸發掃描,進行資產收集。

啟用MASSCAN

啟用端口探測列表配置:

 

 這兩個配置都是用來探測端口的,默認是ICMP方式,只對存活的IP地址進行指定端口的探測,MASSCAN方式探測1-65535的端口,第一個方框內為路徑地址,第二個方框內為發包速率。

服務類型識別配置:

 用於識別開放端口上所運營的服務。

cms識別規則配置:

 CMS英文全稱是:Content Management System 中文名稱是: 網站內容管理系統 (CMS最擅長的就是建設網站,最流行的CMS有:國外的:Wordpress,Drupal,Joomla,這是國外最流行的3大CMS。國內則是DedeCMS和帝國,PHPCMS等)。CMS識別原理就是得到一些CMS的一些固有特征,通過得到這個特征來判斷CMS的類別。 比如使用MD5識別和正則表達式識別的方式,就是用特定的文件路徑訪問網站,獲得這個文件的MD5或者用正則表達式匹配某個關鍵詞,如果匹配成功就說明這個是這個CMS。 所以,這個識別的成功率是根據我們的字典來的。

代碼語言識別規則配置:

 

 用於識別web網站的開發語言,通過響應頭、文件等

組件容器識別配置:

 用於識別web的容器、中間件等組件。對容器、中間件等不了解,網上搜了一下:容器作為操作系統和應用程序之間的橋梁,給處於其中的應用程序組件提供一個環境,使應用程序直接跟容器中的環境變量交互,不必關注其它系統問題。

下面是配置更新頁面的視圖函數以及數據庫里面的內容:

# 配置更新異步
@app.route('/updateconfig', methods=['get', 'post'])
@logincheck
def UpdateConfig():
    rsp = 'fail'
    name = request.form.get('name', 'default')  # 配置名
    value = request.form.get('value', '')       # 配置值
    conftype = request.form.get('conftype', '') # 配置類型
    print name,"\n",value,"\n",conftype
    # 根據name來判斷是哪個配置,就從數據庫去取對應的值,然后把提交過來的value加上去更新
    # 如果三個值都存在
    if name and value and conftype:
        # 判斷所要更新的配置

        # 端口列表或MAsscan配置
        if name == 'Masscan' or name == 'Port_list':
            origin_value = Mongo.coll['Config'].find_one({'type': 'nascan'})["config"][name]["value"]
            value = origin_value.split('|')[0] + '|' + value
        # 判斷是否啟用存活探測ICMP
        elif name == 'Port_list_Flag':
            name = 'Port_list'
            origin_value = Mongo.coll['Config'].find_one({'type': 'nascan'})["config"]['Port_list']["value"]
            value = value + '|' + origin_value.split('|')[1]
        # 判斷是否啟用MASSCAN
        elif name == 'Masscan_Flag':
            name = 'Masscan'
            path = Mongo.coll['Config'].find_one({'type': 'nascan'})["config"]["Masscan"]["value"]
            if len(path.split('|')) == 3:
                path = path.split('|')[1] + "|" + path.split('|')[2]
            else:
                path = path.split('|')[1]
            if value == '1':
                value = '1|' + path
            else:
                value = '0|' + path
        result = Mongo.coll['Config'].update({"type": conftype}, {'$set': {'config.' + name + '.value': value}})
        if result:
            rsp = 'success'
    return rsp
配置更新

 

三:統計頁面

配置頁面完成后,立即開始進行資產探測、可以在統計頁面看到資產探測結果(獲取數據庫數據並在前端進行展示):

下面是加了注釋的視圖函數源碼

# 統計頁面
@app.route('/analysis')
@logincheck
def Analysis():
    # distinct獲取集合中指定字段的不重復值,以集合的形式返回,就是去重
    ip = len(Mongo.coll['Info'].distinct('ip'))
    print 'ip總數', ip
    # 獲取數量
    record = Mongo.coll['Info'].find().count()
    print '記錄總數', record
    # 獲取任務數量
    task = Mongo.coll['Task'].find().count()
    print '任務總數', task
    # group是mongodb中自帶的三種聚合函數之一
    # count:簡單統計集合中符合某種條件的文檔數量。
    # distinct:用於對集合中的文檔針進行去重處理。
    # group:用於提供比count、distinct更豐富的統計需求,可以使用js函數控制統計邏輯
    vul = int(Mongo.coll['Plugin'].group([], {}, {'count': 0},'function(doc,prev){prev.count = prev.count + doc.count}')[0]['count'])
    plugin = Mongo.coll['Plugin'].find().count()
    print "插件總數", plugin
    vultype = Mongo.coll['Plugin'].group(['type'], {"count":{"$ne":0}}, {'count': 0},'function(doc,prev){prev.count = prev.count + doc.count}')
    print "漏斗類型", vultype
    # sort根據date字段降序排列,limit指定讀取的數據數量
    cur = Mongo.coll['Statistics'].find().sort('date', -1).limit(30)
    trend = []
    for i in cur:
        trend.append(
            {'time': i['date'], 'add': i['info']['add'], 'update': i['info']['update'], 'delete': i['info']['delete']})
    # 找到Heartbeat集合中的兩個文檔
    vulbeat = Mongo.coll['Heartbeat'].find_one({'name': 'load'})
    scanbeat = Mongo.coll['Heartbeat'].find_one({'name': 'heartbeat'})
    if vulbeat == None or scanbeat == None:
        taskpercent = 0
        taskalive = False
        scanalive = False
    else:
        taskpercent = vulbeat['value'] * 100
        taskalive = (datetime.now() - vulbeat['up_time']).seconds
        scanalive = (datetime.now() - scanbeat['up_time']).seconds
        taskalive = True if taskalive < 120 else False
        scanalive = True if scanalive < 120 else False
    # aggregate主要用於處理數據(諸如統計平均值,求和等),並返回計算后的數據結果。有點類似sql語句中的 count(*)。
    server_type = Mongo.coll['Info'].aggregate(
        [{'$group': {'_id': '$server', 'count': {'$sum': 1}}}, {'$sort': {'count': -1}}])
    web_type = Mongo.coll['Info'].aggregate([{'$match': {'server': 'web'}}, {'$unwind': '$webinfo.tag'},
                                             {'$group': {'_id': '$webinfo.tag', 'count': {'$sum': 1}}},
                                             {'$sort': {'count': -1}}])
    #把數據傳到analysis.html頁面進行渲染
    return render_template('analysis.html', ip=ip, record=record, task=task, vul=vul, plugin=plugin, vultype=vultype,
                           trend=sorted(trend, key=lambda x: x['time']), taskpercent=taskpercent, taskalive=taskalive,
                           scanalive=scanalive, server_type=server_type, web_type=web_type)
統計頁面視圖函數

 

三:搜索頁面

資產探測完成后,就可以根據搜索規則,搜索需要的端口,ip等。

比如:查看所有開放25端口的IP,在搜索框輸入port:25。 查看指定IP、IP段等

 

 

 前端輸入搜索條件后,后端在search.html接受,並命名為q:

<input type="text" class="form-control"
     placeholder="Example:  ip: 192.168.1.1; port: 22"
     style="color: #797979;" id="filter" name="q">

 

搜索完成后,會直接對結果展示

四:任務頁面

搜索出結果后,可以選中其中的一個或多個(作為目標),然后新增任務,選擇插件類型,根據選擇的插件數量創建任務,后端就會進行任務掃面掃描,點擊任務名稱,即可查看該任務的詳情。

 

 

下面是加了注釋的視圖函數源碼

# 新增任務異步
@app.route('/addtask', methods=['get', 'post'])
@logincheck
def Addtask():
    # 先獲取了頁面傳了的值 先默認result為fail
    # 沒有plugin的話直接返回fail
    # 有的話,先判斷結果集是否全選,將結果集的ip和port都加入列表,否則將當前頁的ip將入列表。
    title = request.form.get('title', '')   # 任務名稱
    plugin = request.form.get('plugin', '') # 從插件列表里所選擇的插件
    condition = unquote(request.form.get('condition', ''))  # 所選結果的ip地址
    print 222222,condition
    plan = request.form.get('plan', 0)  # 執行周期
    print 33333,plan
    ids = request.form.get('ids', '')   # 所選地址的 ip:端口
    print 44444,ids
    isupdate = request.form.get('isupdate', '0')    # 是否自動更新列表
    resultcheck = request.form.get('resultcheck', '0')  # 結果集是否全選
    print title,plugin,condition,plan,ids,isupdate,resultcheck
    result = 'fail'
    if plugin:
        targets = []
        if resultcheck == 'true':  # 結果集全選
            list = condition.strip().split(';')
            print list
            query = querylogic(list)
            cursor = Mongo.coll['Info'].find(query)
            for i in cursor:
                tar = [i['ip'], i['port']]
                targets.append(tar)
        else:  # 當前頁結果選擇
            for i in ids.split(','):
                tar = [i.split(':')[0], int(i.split(':')[1])]
                targets.append(tar)
        temp_result = True
        for p in plugin.split(','):
            query = querylogic(condition.strip().split(';'))
            item = {'status': 0, 'title': title, 'plugin': p, 'condition': condition, 'time': datetime.now(),
                    'target': targets, 'plan': int(plan), 'isupdate': int(isupdate), 'query': dumps(query)}
            # 插入到數據庫
            insert_reuslt = Mongo.coll['Task'].insert(item)
            if not insert_reuslt:
                temp_result = False
        if temp_result:
            result = 'success'
    return result
下發任務源碼

 

五:插件頁面

 插件的展示主要是獲取數據庫內容,並傳到前端,主要看一下插件的增加

# 插件列表頁
@app.route('/plugin')
@logincheck
def Plugin():
    # 獲取前端頁面
    page = int(request.args.get('page', '1'))
    print 1111,page
    # 從數據庫里面找到有關插件數據
    cursor = Mongo.coll['Plugin'].find().limit(page_size).skip((page - 1) * page_size)
    # 在前端頁面展示
    return render_template('plugin.html', cursor=cursor, vultype=cursor.distinct('type'), count=cursor.count())
插件展示源碼

 

插件有兩種格式,一是json格式,二是py腳本格式。

json格式的上傳較為簡單,只需要在前端填寫對應的內容就行

 

 

 而py腳本方式,則需要按指定的格式編寫代碼,大概如下:

def get_plugin_info():
    plugin_info = {
        "name": "MySQL弱口令",
        "info": "導致數據庫敏感信息泄露,嚴重可導致服務器直接被入侵。",
        "level": "高危",
        "type": "弱口令",
        "author": "wolf@YSRC",
        "url": "",
        "keyword": "server:mysql",
        "source": 1
    }
    return plugin_info

def get_hash(password, scramble):
    hash_stage1 = hashlib.sha1(password).digest()
    hash_stage2 = hashlib.sha1(hash_stage1).digest()
    to = hashlib.sha1(scramble + hash_stage2).digest()
    reply = [ord(h1) ^ ord(h3) for (h1, h3) in zip(hash_stage1, to)]
    hash = struct.pack('20B', *reply)
    return hash

def get_scramble(packet):
    tmp = packet[15:]
    m = re.findall("\x00?([\x01-\x7F]{7,})\x00", tmp)
    if len(m) > 3: del m[0]
    scramble = m[0] + m[1]
    try:
        plugin = m[2]
    except:
        plugin = ''
    return plugin, scramble


def get_auth_data(user, password, scramble, plugin):
    user_hex = binascii.b2a_hex(user)
    pass_hex = binascii.b2a_hex(get_hash(password, scramble))
    if not password:
        data = "85a23f0000000040080000000000000000000000000000000000000000000000" + user_hex + "0000"
    else:
        data = "85a23f0000000040080000000000000000000000000000000000000000000000" + user_hex + "0014" + pass_hex
    if plugin: data += binascii.b2a_hex(
        plugin) + "0055035f6f73076f737831302e380c5f636c69656e745f6e616d65086c69626d7973716c045f7069640539323330360f5f636c69656e745f76657273696f6e06352e362e3231095f706c6174666f726d067838365f3634"
    len_hex = hex(len(data) / 2).replace("0x", "")
    auth_data = len_hex + "000001" + data
    return binascii.a2b_hex(auth_data)


def check(ip, port, timeout):
    socket.setdefaulttimeout(timeout)
    user_list = ['root']
    for user in user_list:
        for pass_ in PASSWORD_DIC:
            try:
                pass_ = str(pass_.replace('{user}', user))
                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                sock.connect((ip, int(port)))
                packet = sock.recv(254)
                # print packet
                plugin, scramble = get_scramble(packet)
                auth_data = get_auth_data(user, pass_, scramble, plugin)
                sock.send(auth_data)
                result = sock.recv(1024)
                if result == "\x07\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00":
                    return u"存在弱口令,賬號:%s,密碼:%s" % (user, pass_)
            except Exception, e:
                if "Errno 10061" in str(e) or "timed out" in str(e): return
py格式插件

 

其中get_plugin_info()與check()方法是必須的,用來獲取插件信息和檢查。

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


免責聲明!

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



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