安全掃描工具​Nmap的系統(tǒng)結(jié)構(gòu)及掃描流程解析
本文檔介紹了Nmap的系統(tǒng)結(jié)構(gòu)及掃描流程,最后重點介紹了Nmap的NSE掃描腳本。
Nmap簡介
Nmap也就是Network Mapper,是一款網(wǎng)絡(luò)連接端掃描軟件,用來掃描網(wǎng)上電腦開放的網(wǎng)絡(luò)連接端。確定哪些服務(wù)運行在哪些連接端,并且推斷計算機運行哪個操作系統(tǒng)。它是網(wǎng)絡(luò)管理員比用的軟件之一,以及用以評估網(wǎng)絡(luò)系統(tǒng)保安,nmap的核心功能有:
主機發(fā)現(xiàn):用于發(fā)現(xiàn)目標主機是否處于活動狀態(tài)。Nmap提供多種檢測機制,可以更有效地辯識主機。
端口掃描:用于掃描主機上端口狀態(tài)。Nmap可以將端口識別為開放(Open)、關(guān)閉(Closed)、過濾(Filtered)、未過濾(Unfiltered)、開放|過濾(Open|Filtered)、關(guān)閉|過濾(Closed|Filtered)。默認情況下,Nmap會掃描1000個常用端口,可以覆蓋大多數(shù)基本應(yīng)用情況。
版本偵測:用于識別端口上運行的應(yīng)用程序與應(yīng)用版本。Nmap目前可以識別數(shù)千鐘中應(yīng)用的簽名,檢測數(shù)百種應(yīng)用協(xié)議。而對于不識別的應(yīng)用,Nmap默認會將應(yīng)用的指紋打印出來,如果用于確知該應(yīng)用程序,那么用戶可以將信息提交到社區(qū),為社區(qū)做貢獻。
操作系統(tǒng)偵測:用于識別目標機的操作系統(tǒng)類型、版本編號及設(shè)備類型。Nmap目前提供了上千種操作系統(tǒng)或設(shè)備的指紋數(shù)據(jù)庫,可以識別通用PC系統(tǒng)、路由器、交換機等設(shè)備類型。
防火墻/IDS規(guī)避:Nmap提供多種機制來規(guī)避防火墻、IDS的屏蔽和檢查,便于秘密地探查目標機的狀況。基本的規(guī)避方式包括:分片/IP誘騙/IP偽裝/MAC偽裝等等。
NSE腳本引擎:NSE是Nmap最強大最靈活的特性之一,可以用于增強主機發(fā)現(xiàn)、端口掃描、版本偵測、操作系統(tǒng)偵測等功能,還可以用來擴展高級的功能如web掃描、漏洞發(fā)現(xiàn)。漏洞利用等等。Nmap使用lua語言來作為NSE腳本語言,目前的Nmap腳本庫已經(jīng)支持400多個腳本。#p#
Nmap的工作流程
Nmap的執(zhí)行流程簡單清晰,主要分為三個階段
準備階段:在其中會執(zhí)行參數(shù)解析、資源分配、基本掃描信息的輸出、端口與地址列表的初始化、NSE環(huán)境準備及pre_scripts的運行等基本的準備操作。
工作階段:然后進入主循環(huán),每次循環(huán)對一組目標地址進行主機發(fā)現(xiàn)、端口掃描、服務(wù)與版本偵測、OS偵測及腳本掃描等操作,直到所有的目標地址都被掃描完畢才推出主循環(huán)
善后階段:在完成所有掃描操作后,調(diào)用post-script完成相應(yīng)處理,然后打印出掃描的最終結(jié)果,并釋放掉分配的資源。
下圖為Nmap的執(zhí)行流程圖
Nmap腳本引擎
Nmap提供了強大的腳本引擎(NSE),以支持Lua編程來擴展Nmap的功能。目前腳本庫已經(jīng)包含400多個常用的Lua腳本,輔助完成Nmap的主機發(fā)現(xiàn)、端口掃描、服務(wù)偵測、操作偵測四個基本功能,并補充了其他掃描能力:如執(zhí)行HTTP服務(wù)詳細信息的探測、暴力破解簡單密碼、檢查常見的漏洞信息等等。如果用戶需要對特定的應(yīng)用做更深入的探究,可以按照NSE腳本格式便攜Lua腳本來增強Nmap的掃描能力。
實現(xiàn)原理
NSE主要分為兩大部分:內(nèi)嵌Lua解釋器與NSE library。
解釋器:Nmap采用嵌入的Lua解釋器來支持Lua腳本語言。Lua語言小巧簡單而且擴展靈活自身的C/C++語言融合。
NSE library:為Lua腳本與Nmap提供了連接,負責(zé)完成基本初始化及提供腳本調(diào)度、并發(fā)執(zhí)行、IO框架及異常處理,并提供了默認的實用的腳本程序。
腳本分類
NSE中提供的Lua腳本分別為不同的類別,根據(jù)官方網(wǎng)站,目前的有14中類別:
auth:負責(zé)處理鑒權(quán)證書(繞開鑒權(quán))的腳本
broadcast:在局域網(wǎng)內(nèi)探查更多服務(wù)開啟狀況,如dhcp/dns/sqlserver等服務(wù)。
brute:提供暴力破解方式,針對常見的應(yīng)用如http/snmp等
default:這是使用-sC或A選項掃描時默認的腳本,提供基本掃描能力
discovery:對網(wǎng)絡(luò)進行更多的信息,如SMB枚舉、SNMP查詢等
dos:用于進行拒絕服務(wù)攻擊
exploit:利用已知的漏洞入侵系統(tǒng)
external:利用第三方的數(shù)據(jù)庫或資源,例如whois解析
fuzzer:模糊測試的腳本,發(fā)送異常的包的目標機,探測出潛在漏洞
intrusive:入侵性的腳本,此類腳本可能引發(fā)對方的IDS/IPS的記錄或屏蔽
malware:探測目標機是否感染了病毒、開啟了后門等信息
safe:此類與instrusive相反,屬于安全性腳本
version:負責(zé)增強服務(wù)與版本掃描功能的腳本
vuln:負責(zé)檢查目標機是否有常見的漏洞,如是否有MS08_067
每種腳本不止屬于一種類型的,具體屬于哪種類型可進入官網(wǎng)查看 http://www.nmap.org
#p#
NSE掃描流程
Nse腳本掃描屬于主循環(huán)流程下的一個部分,其代碼流程圖如下:
初始化流程
在命令行參數(shù)中指定腳本(–script/-sC)或指定-A選項或指定-sV選項,都會觸發(fā)Nmap啟動腳本引擎。其中-A選項表示全面掃描,會調(diào)用default類別的腳本掃描;而-sV選項表示應(yīng)用與版本偵測,會調(diào)用Version類別的腳本,輔助偵測服務(wù)詳細信息。
nmap_main()函數(shù)中,若判斷需要啟動腳本引擎,這首先需要調(diào)用open_nse()函數(shù)進行NSE環(huán)境的準備,首先要創(chuàng)建luaState(管理Lua解釋器的執(zhí)行的全局變量),然后調(diào)用init_main()函數(shù)進行詳細的初始化過程。
進入init_main()函數(shù),首先加載Lua標準版庫與Nmap的擴展庫,隨后準備參數(shù)環(huán)境,然后加載并執(zhí)行nse_main.lua文件。
nse_main.lua腳本為后續(xù)的腳本執(zhí)行準備Lua環(huán)境,加載用戶選擇的需要調(diào)用的腳本(例如,用戶–script discovery,那么會將該類別中所有的腳本加載進來),返回一個main()函數(shù)對象給init_main(),該main()是否后續(xù)腳本掃描需要的主函數(shù),被保存在Lua的環(huán)境的注冊表中。
在nse_main.lua中,定義兩個核心的類,Script和Thread,Script用于管理NSE腳本,當(dāng)新的腳本被加載時,調(diào)用Script.new創(chuàng)建腳本對象,該對象被保存下來在后續(xù)的掃描過程中使用;Thread用于管理腳本的執(zhí)行,該類中也包含對腳本健全性的檢查(sanitycheck,如是否包含Action函數(shù),4.4會講到)。在腳本執(zhí)行時,如果腳本之間存在依賴關(guān)系,那么會將基礎(chǔ)的無依賴的腳本統(tǒng)一執(zhí)行完畢,再執(zhí)行依賴性的腳本。
腳本掃描流程
執(zhí)行腳本掃描時,從nmap_main()中調(diào)用script_scan()函數(shù)。
在進入script_scan()后,會標記掃描階段類型,然后進入到初始化階段返回的main()函數(shù)(來自nse_main.lua腳本中的main)中,在函數(shù)中解析具體的掃描類型。
main()函數(shù)負責(zé)處理三種類型的腳本掃描:預(yù)掃描(SCRIPT_PRE_SCAN)、腳本掃描(SCRIPT_SCAN)、后掃描(SCRIPT_POST_SCAN)。預(yù)掃描即在Nmap調(diào)用的最前面(沒有進行主機發(fā)現(xiàn)、端口掃描等操作)執(zhí)行的腳本掃描,通常該類掃描用于準備基本的信息,例如到第三服務(wù)器查詢相關(guān)的DNS信息。而腳本掃描,是使用NSE腳本來掃描目標主機,這是最核心的掃描方式。后掃描,是整個掃描結(jié)束后,做一些善后處理的腳本,比如優(yōu)化整理某些掃描。
在main()函數(shù)中核心操作由run函數(shù)負責(zé)。而run()函數(shù)的本身設(shè)計用于執(zhí)行所有同一級別的腳本(根據(jù)依賴關(guān)系劃分的級別),直到所有線程執(zhí)行完畢才退出。
run()函數(shù)中實現(xiàn)三個隊列:執(zhí)行隊列(Running Queue)、等待隊列(Waiting Queue)、掛起隊列(Pending Queue),并管理三個隊列中線程的切換,直到全部隊列為空或出錯而退出。#p#
NSE腳本結(jié)構(gòu)
NSE的使用Lua腳本,并且配置固定格式,以減輕用戶編程負擔(dān),通常的一個腳本氛圍幾個部分:
Description 字段:描述腳本功能的字符串,使用雙層方括號表示。
Comment 字段:以__開頭的行,描述腳本輸出格式
Author 字段:描述腳本作者
License 字段:描述腳本使用許可證,通常配置為Nmap相同的license
Categories 字段:描述腳本所屬的類別,以對腳本的調(diào)用進行管理。
Rule 字段:描述腳本執(zhí)行的規(guī)則,也就是確定觸發(fā)腳本執(zhí)行的條件。在Nmap中有四種類型的規(guī)則。
A.Prerule()用于在Nmap沒有執(zhí)行掃描之前觸發(fā)腳本執(zhí)行,這類腳本腳本并不需要用到任何Nmap掃描的結(jié)果;
B.Hostrule()用在Nmap執(zhí)行完畢主機發(fā)現(xiàn)后觸發(fā)的腳本,根據(jù)主機發(fā)現(xiàn)的結(jié)果來觸發(fā)該類腳本
C.Postrule用于Nmap執(zhí)行端口掃描或版本偵測時觸發(fā)的腳本,例如檢測到某個端口時觸發(fā)某個腳本執(zhí)行以完成更詳細的偵查
D.Postrule用于Nmap執(zhí)行完畢所有掃描后,通常用于掃描結(jié)果的數(shù)據(jù)提取和整理。
Action 字段:腳本執(zhí)行的具體內(nèi)容。當(dāng)腳本通過rule字段檢查被觸發(fā)執(zhí)行時,就會調(diào)用action字段定義的函數(shù)
下面以broadcast-db2-discover.nse腳本為例說明:
local nmap = require "nmap"
local stdnse = require "stdnse"
local string = require "string"
local table = require "table"
local target = require "target"
/*本腳本需要調(diào)用的庫函數(shù),可以在Nmap/nselib/文件下查看相關(guān)函數(shù)*/
description = [[
Attempts to discover DB2 servers on the network by sending abroadcast request to port 523/udp.
]]
/*description字段:發(fā)送一個廣播包,試圖在網(wǎng)絡(luò)中發(fā)現(xiàn)DB2服務(wù)器*/
---
-- @usage
-- nmap --script db2-discover
--
-- @output
-- Pre-scan script results:
-- | broadcast-db2-discover:
-- | 10.0.200.132(UBU804-DB2E) - IBM DB2 v9.07.0
-- |_ 10.0.200.119(EDUSRV011) - IBM DB2 v9.07.0
-- Version 0.1
-- Created 07/10/2011 - v0.1 - created by Patrik Karlsson<patrik@cqure.net>
/*comment字段:描述了輸入輸出格式*/
author = "Patrik Karlsson"
/*author字段*/
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
/*license字段*/
categories = {"broadcast", "safe"}
/*categories字段:此腳本的分類類型,輸入broadcast和safe兩種*/
prerule = function() return true end
/*執(zhí)行腳本規(guī)則的條件,預(yù)掃描階段執(zhí)行*/
--- Converts the prodrel server string to a version string
--
-- @param server_version string containing the product release
-- @return ver string containing the version information
/*解釋下面函數(shù)的輸入輸出內(nèi)容*/
local function parseVersion( server_version )
local pfx = string.sub(server_version,1,3) /*獲取server_version字符串的第一到第三個字符*/
if pfx =="SQL" then
localmajor_version = string.sub(server_version,4,5) /*如果pfx字符串為SQL,則major_version獲取server_version版本的第四到第五個字符*/
-- strip theleading 0 from the major version, for consistency with
--nmap-service-probes results
ifstring.sub(major_version,1,1) == "0" then
major_version= string.sub(major_version,2) /*若major_version字符串首字母為0,則去除*/
end
localminor_version = string.sub(server_version,6,7) /*minor_version獲取server_version字符串的第6到第7個字符*/
local hotfix =string.sub(server_version,8) /*hotfix獲取server_version字符串的第8個字符*/
server_version =major_version .. "." .. minor_version .. "." .. hotfix /*給server_version字符串附值*/
else
return"Unknown version"
end
return ("IBM DB2v%s"):format(server_version) /*函數(shù)返回值*/
end
action = function() /*腳本執(zhí)行函數(shù)*/
local DB2GETADDR ="DB2GETADDRSQL09010"
local socket =nmap.new_socket("udp")
local result = {}
local host, port ="255.255.255.255", 523
/*定義字符類型,并附初始值*/
socket:set_timeout(5000) /*若超過5s,數(shù)據(jù)發(fā)送不出去,則發(fā)送失敗*/
local status =socket:sendto( host, port, DB2GETADDR ) /*調(diào)用socket:sendto函數(shù),可以在nselib/nmap.nsedoc文檔下查看其返回值,此處是發(fā)送一個廣播包,到523端口*/
if ( not(status) ) thenreturn end /*若發(fā)送失敗,則終止操作*/
while(true) do
local data
status, data =socket:receive()
if( not(status) )then break end /*若返回信息錯誤,則終止操作*/
local version,srvname = data:match("DB2RETADDR.(SQL%d+).(.-)") /*根據(jù)返回的信息,提取version和srvname字段*/
local _, ip
status, _, _, ip,_ = socket:get_info()
if ( not(status)) then return end /*若遠程套接字接口返回信息錯誤,則終止操作*/
iftarget.ALLOW_NEW_TARGETS then target.add(ip) end /*若target.ALLOW_NEW_TARGETS函數(shù)為真,則在Nmap掃描隊列中添加此IP*/
if ( status )then
table.insert(result, ("%s - Host: %s; Version: %s"):format(ip, srvname,parseVersion( version ) ) ) /*在result數(shù)組中插入相關(guān)信息*/
end
end
socket:close()
returnstdnse.format_output( true, result ) /*輸出掃描結(jié)果*/
end

























