1. 程式人生 > 其它 >企業安全建設之路:埠掃描(下)

企業安全建設之路:埠掃描(下)

0x00、前言

在企業安全建設過程當中,我們也不斷在思考,做一個什麼樣的埠掃描才能企業業務需求。同時,伴隨著企業私有云、混合雲以及公有云業務部署環境的不斷變化,我們適當也要對掃描策略做調整。前期的埠掃描設計在http://www.freebuf.com/articles/rookie/128526.html

在本文各個部分有所變動。

0x01、詳細設計

@1、各個模組之間的互動:

一開始都是把產品想的特別完美,

(1) Web控制端 (2) worker工作節點 (3) 儲存掃描結果(maybe: HDFS)

這樣實現起來比較麻煩,當時說使用celery做排程,後來發現,celery對django有版本要求,超過1.10版本不成。等等現實問題。其實celery也是redis做排程資料同步。有時間可以自己做。

其實Web控制端和worker可以使用資料庫做互動。使用者通過Web控制端設定掃描策略和檢視報表。Worker讀取資料庫中的配置資訊,執行掃描任務,把掃描結果儲存到資料庫。

@2、功能需求

在對埠掃描功能的選型上,為啥選擇nmap,

(1) 很多商用掃描器也是整合nmap掃描結果,例如:rapid7 Vulnerability Management。 (2) nmap掃描速度,肯定沒有masscan、Zmap快,但是掃描結果有對服務banner和版本的探測,更重要的是有作業系統的探測。在雲平臺部署zmap等無狀態掃描,會瞬間發出大量資料包,公有云EIP頻寬QoS超過會立刻丟棄,對掃描結果有很大影響。 (3) libnmap 對掃描結果解析的相對完美,方便的提取我想要的資料到資料庫中。

埠掃描後,我們還能做什麼?

(1) 個人認為第一需求就是對新暴發的漏洞做企業內部評估。前幾天的WannaCry就是445埠對外開發又可能觸發MS-17-010的RCE。這裡我集成了巡風漏洞掃描元件。 (2) 評估高危埠變化趨勢,也是衡量企業安全管理人員工作成果的一個手段。 (3) 對企業內部部門漏洞分佈有清晰的瞭解

0x02、互動設計

與使用者互動部分,因為是安全管理員用,所以簡單做。Axure是一個好的互動工具,可以幫助你梳理業務邏輯。

按照模組分:

(1)掃描配置

(2)掃描報表

0x03、前端實現

(1)開發環境建立:

brew install nodejs
npm install webpack –g
npm install --global vue-cli
vue init webpack CloudPScan
cd CloudPScan
npm install
npm install vue-resource
npm install element-ui

設定代理 config/dev.index.js
module.exports = {
  //...
 dev: {
    proxyTable: {
     // proxy all requests starting with /api to http://127.0.0.1:8000
     '/api': {
       target: 'http://127.0.0.1:8000',
       changeOrigin: true,
     }
}
}

(2)建立頁面路由

import Vue from 'vue'
import Routerfrom 'vue-router'

import LoginViewfrom '@/components/LoginView'
import MainViewfrom '@/components/MainView'
import ScanSettingViewfrom '@/components/ScanSettingView'
import ScanReportViewfrom '@/components/ScanReportView'

import ElementUIfrom 'element-ui'
import 'element-ui/lib/theme-default/index.css'

import VueResourcefrom 'vue-resource'

Vue.use(ElementUI)
Vue.use(Router)
Vue.use(VueResource)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'LoginView',
      component: LoginView
    }
    , {
      path: '/MainView',
      name: 'MainView',
      component: MainView,
      children: [{
        name: 'ScanSettingView',
        path: '/ScanSettingView',
        component: ScanSettingView
      }, {
        name: 'ScanReportView',
        path: '/ScanReportView',
        component: ScanReportView
      }]
    }
  ]
})

(3)登陸頁面

<template>
  <div class="logincontainer" align="center">
    <div class="form-signin" >
      <img  alt="雲平臺掃描系統">
    </div>
    <div class="form-signin--form" align="center">
      <el-tabs>
        <el-form label-position="center" @submit.native.prevent="doLogin" auto-complete="on" label-width="80px">
          <el-form-item label="使用者" :required ='true'>
            <el-input v-model="params.username" auto-complete="on"></el-input>
          </el-form-item>
          <el-form-item label="密碼" :required ='true'>
            <el-input type="password" v-model="params.password" auto-complete="on"></el-input>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" native-type="submit" style="width:180px;text-align:center;">登入</el-button>
            <p v-if="fail" class="alert alert-danger">
              {{ msg }}
            </p>
          </el-form-item>
        </el-form>
      </el-tabs>
      <div class="sl-login_copyright">
        GSGSoft Research <br/>© 2017 GSGSoft Tech.
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: 'LoginView'
  , data: function () {
    return {
      fail: true
      , msg: ''
      , params: {
        username: ''
        , password: ''
      }
    }
  }
  , methods: {
    doLogin () {  //這個地方的處理就忽略了,其實就是請求查詢資料庫是否匹配提交的賬號和密碼,如果匹配然後跳轉
      this.$router.replace({
        path: '/MainView'
      })
    }
  }
  , created () {
  }
}
</script>

【詳細內容可以點選原文檢視】

0x04、後端實現

(1)資料庫設計

配置儲存表:主要是儲存使用者輸入的掃描配置記錄,包括任務名稱、掃描開始IP、掃描結束IP、掃描週期、掃描進度。

CREATE TABLE scanconf
(
   id INTEGER DEFAULT nextval('table_name_id_seq'::regclass) PRIMARY KEYNOT NULL,
   ipconf_startip TEXT,
   ipconf_endip TEXT,
   looptime INTEGER,
   task_id TEXT,
   scanstate TEXT
);
CREATE UNIQUE INDEX table_name_id_uindex ONscanconf (id);

資料儲存表:包含任務名稱、建立時間、IP地址、埠、服務、產品、產品版本、產品額外資訊、作業系統、對應使用者名稱稱、對應的使用者部門。

CREATE TABLE scanresult_20170609
(
   task_id TEXT,
   ctime TEXT,
   address TEXT,
   port TEXT,
   service TEXT,
   product TEXT,
   product_version TEXT,
    product_extrainfo TEXT,
   os TEXT,
   eip TEXT,
   business TEXT
);

漏洞型別描述:主要是把漏洞資訊記錄到資料庫中。例如:

st2_eval Struts2

遠端程式碼執行

可直接執行任意程式碼,

進而直接導致伺服器被入侵控制。

緊急

程式碼執行

wolf@YSRC

http://www.shack2.org/article/1374154000.html

tag:tomcat

CREATE TABLE vultype
(
   id INTEGER DEFAULT nextval('vultype_id_seq'::regclass) PRIMARY KEY NOTNULL,
   add_time TEXT,
   filename TEXT,
   name TEXT,
   info TEXT,
   level TEXT,
   type TEXT,
   author TEXT,
   url TEXT,
   keyword TEXT
);
CREATE UNIQUE INDEX vultype_id_uindex ONvultype (id);

掃描結果儲存表:例如:

x.x.21.116

heartbleed_poc

存在心臟出血漏洞

2017-05-27 11:26:56

CREATE TABLE vulresult
(
   id INTEGER DEFAULT nextval('vulresult_id_seq'::regclass) PRIMARY KEY NOTNULL,
   address TEXT,
   vulname TEXT,
   result TEXT,
   ctime TEXT
);
CREATE UNIQUE INDEX vulresult_id_uindex ONvulresult (id);

(2)程式碼實現-埠掃描程式碼

OpenAPI部分:

Urls.py

urlpatterns = [

    url(r'^api/config/newtask/$', ConfigAPI.as_view()),
    url(r'^api/action/doscan/$', ScanAPI.as_view()),
    url(r'^api/config/tasklist/id$', ScanconfListAPI.as_view()),
    url(r'^api/config/deltask/$', ConfigDelAPI.as_view()),
]

建立掃描任務

class ConfigAPI(APIView):
    def post(self, request, format=None):
        m_task_id = request.POST.get('task_id')
        db_tasks = scanconf.objects.filter(task_id=m_task_id)
        if db_tasks.exists():
            return error(err="exists", msg="task name exists")
        else:
            ser = ScanconfSerializer(data=request.data)
            print request.data
            if ser.is_valid():
                ser.save()
                return Response(ser.data)
            return Response(ser.errors)

刪除掃描任務

class ConfigDelAPI(APIView):
    def post(self, request, format=None):
        data = request.data
        m_task_id = data['task_id']
        db_tasks = scanconf.objects.filter(task_id=m_task_id).delete()
        return success("success") 

啟動掃描任務

class ScanAPI(APIView):
    def post(self, request, format =None):
        data = request.data
        m_task_id = data['task_id']
        print m_task_id
        db_tasks = scanconf.objects.filter(task_id=m_task_id)
        if db_tasks.exists():
            try:
                threading.Thread(target=ScanExtIP.doscan, args=(m_task_id,)).start()
            except:
                print traceback.print_exc()
            return Response("success")
        return Response("doscan failure no task in db") 

列舉掃描任務

class ScanconfListAPI(APIView):
    def get(self, request, format=None):
        print request.GET.get("count")
        cursor = scanconf.objects.all()
        return Response(paginate_data(request, cursor, ScanconfSerializer)) 

掃描執行

def Scan():
    try:
        global g_queue
        global g_task_id
        tableName = "%s_%s" % ("scanresult", time.strftime("%Y%m%d"))
        num = '0.0'
        curS = connS.cursor()
        curS.execute("update scanconf SET scanstate = %s where task_id = %s", (num, g_task_id))
        connS.commit()
        cur1 = conn1.cursor()
        while not g_queue.empty():
            item = g_queue.get()
            nm = NmapProcess(item, "-sV -O --min-rate 2000 --max-rtt-timeout 100ms")
            nm.sudo_run()
            ctime = strftime("%Y-%m-%d %H:%M:%S", gmtime())
            nmap_report = NmapParser.parse(nm.stdout)
            for scanned_hosts in nmap_report.hosts:
                print scanned_hosts.address
                if len(scanned_hosts.os.osmatch()) > 0:
                    print scanned_hosts.os.osmatch()[0]
                for serv in scanned_hosts.services:
                    if serv.state == 'open':
                        if len(scanned_hosts.os.osmatch()) > 0:
                            sql = "INSERT INTO %s (task_id,ctime, address,port,service,product,product_version,product_extrainfo,os) VALUES ('%s','%s','%s','%s','%s','%s','%s','%s','%s')"
                            sqlCmd = sql%(tableName,g_task_id,ctime,scanned_hosts.address,str(serv.port),serv.service,serv.service_dict.get("product", ""),serv.service_dict.get("version", ""),serv.service_dict.get("extrainfo", ""),scanned_hosts.os.osmatch()[0])
                        else:
                            sql = "INSERT INTO %s (task_id,ctime, address,port,service,product,product_version,product_extrainfo,os) VALUES ('%s','%s','%s','%s','%s','%s','%s','%s','%s')"
                            sqlCmd = sql%(tableName,g_task_id,ctime,scanned_hosts.address,str(serv.port),serv.service,serv.service_dict.get("product", ""),serv.service_dict.get("version", ""),serv.service_dict.get("extrainfo", ""),'NULL')
                        cur1.execute(sqlCmd)
                        conn1.commit()
            print "size = ", g_queue.qsize()
            g_size = g_queue.qsize()
            num = 100 - round(float(g_size) / float(g_totalsize) * 100, 0)
            print num, g_size, g_totalsize
            curS = connS.cursor()
            curS.execute("update scanconf SET scanstate = %s where task_id = %s", (num, g_task_id))
            connS.commit()
        return "ok"
    except Exception,e:
        print e
        return e

def CreateTable():
    curC = connC.cursor()
    sqlCreate = "create table if not exists %s ( 
                 task_id TEXT,
                 ctime TEXT,
                 address TEXT,
                 port TEXT,
                 service TEXT,
                 product TEXT ,
                 product_version TEXT,
                 product_extrainfo TEXT,
                 os TEXT,
                 eip TEXT,
                 business TEXT
                 )"
    tableName = "%s_%s"%("scanresult", time.strftime("%Y%m%d"))
    sqlCmd = sqlCreate%tableName
    curC.execute(sqlCmd)

def doscan(task_id):
    global g_queue
    global g_task_id
    listThread = []
    cur = conn.cursor()
    querySQL = "select id,ipconf_startip,ipconf_endip,looptime from scanconf WHERE task_id = '{}'".format(task_id)
    cur.execute(querySQL)
    rows = cur.fetchall()
    for row in rows:
        iplist(row[1],row[2])
    g_task_id =task_id
    conn.commit()
    conn.close()

    CreateTable()
    for i in xrange(g_threadNum):
        thread = ScanThread(Scan)
        thread.start()
        listThread.append(thread)

    for thread in listThread:
        thread.join()
        print thread

    return "ok" 

漏洞掃描部分:主要是整合巡風漏洞系統的VulScan.py 只是把mongodb資料庫換成了postgresql,就不在這裡累述。高危埠變化趨勢:這部分說一下邏輯,因為程式碼實在太長了。就是從資料庫中查詢最近7天的高危埠資料。組合成json的形式返回給全端。

b = json.dumps([{"name": "mysql", "data": list1},                        
{"name": "ms-ql-s", "data": list2},                        
{"name": "ibm-db2", "data": list3},                        
{"name": "oracle", "data": list4},                        
{"name": "redis", "data": list5},                        
{"name": "mongodb", "data": list6},                        
{"name": "day", "data": list7}])        
return HttpResponse(b) 

0x05、部署雲主機的選擇

由於使用了多執行緒,對CPU記憶體要求都比較高,經過綜合對比選擇金山雲大米主機。2 core,4G記憶體,100G SSD,1元用7天,買4個月贈送3個月。

大致的部署架構:

nginx.conf

server 
{   
  listen      80;   
  server_name x.x.10x.1x2;    
  charset     utf-8;   
  client_max_body_size 75M;    
  location /api {       
    proxy_pass http://127.0.0.1:9001;    
  }   
  location / {        
    root /var/CloudPScan/dist;        
    try_files $uri $uri/ /index.html;    
  }
}

uwsgi.ini (uwsgi使用ini檔案啟動)

[uwsgi]
http=127.0.0.1:9001
chdir=/var/CloudPScan/
master=True
pidfile=CloudPScan-master.pid
vacuum=True
max-requests=5000
daemonize=CloudPScan.log
env = LANG=en_US.UTF-8
wsgi-file = CloudPScan/wsgi.py 

伺服器安裝:

yum install epel-releaseyum 
install python-pip python-devel nginx gcc
pip install --upgrade pip
pip install uwsgi
systemctl start uwsgi
cd /etc/nginx/sites-enabled
vim CloudPScan.conf
sudo nginx -t
systemctl start nginxsystemctl enable nginxyum 
install postgresql-serverpostgresql-devel postgresql-contrib
postgresql-setup initdbsystemctl start postgresql
pip install -U django==1.10.0
pip install djangorestframework==3.3.2
pip install requests
pip install python-libnmap
yum install nmap
systemctl stop firewalld.service 

0x06、總結

整個coding的過程比較匆忙,程式碼中也有很多地方不完善,還請各位大牛口下留情。本文從詳細設計、互動設計、前端程式碼實現、後端程式碼實現、部署等環節,完整的描述了一個產品的產生過程。最後一點想說,產品經理和程式設計師需要相互體諒,都不容易。