Nmap腳本(nse)原理和編寫

Nmap腳本引擎原理

一、NSE介紹

  雖然Nmap內嵌的服務於版本探測已足夠強大,但是在某些情況下我們需要多倫次的交互才能夠探測到服務器的信息,這時候就需要自己編寫NSE插件實現這個功能。NSE插件能夠完成網絡發現、複雜版本探測、脆弱性探測、簡單漏洞利用等功能。

腳本掃描通過選項被激活
-sC: 使通用scripts生效
--script: 指定自己的腳本文件
--script-trace: 查看腳本執行過程
-A: 同時進行版本探測和腳本掃描

爲了不進行主機發現也不進行端口掃描,直接使用自定義的腳本探測。可以使用下面的選項:
-Pn -sn --script

--script-args和--script-args-file,指定腳本要讀入的參數
nmap --script snmp-sysdescr --script-args snmpcommunity=admin example.com

nmap --script default,safe 加載default和safe類腳本。
參考: https://nmap.org/book/nse-usage.html#nse-categories

  腳本文件分類:brute、default、dos、safe、exploit等,具體的可查看:https://nmap.org/book/nse-usage.html#nse-categories

  根據腳本的運行階段不同分爲四類:Prerule scripts、Host scripts、Service scripts、Postrule scripts。

  應用最多的就是Service scripts,Post scripts對Nmap輸出進行格式化輸出,Host scripts這個階段在Nmap運行完主機發現、端口掃描、版本探測、OS偵測後執行;Prerule scripts執行一些資源操作,先於各探測執行。

二、Nmap執行原理圖

        由上圖可以看到四類腳本的運行階段,以及他們的功能。

  1)在nmap_main裏面,調用init_main()進行詳細的初始化過程,加載Lua標準庫與Nmap擴展庫,準備參數環境,加載並執行nse_main.lua文件;這個文件加載用戶選擇的腳本文件,執行完之後返回函數對象給init_main(),被保存到Lua註冊表中。

  2)在nse_main.lua中,定義兩個核心的類,Script和Thread,Script用於管理NSE腳本,當新的腳本被加載時,調用 Script.new創建腳本對象,該對象被保存下來在後續的掃描過程中使用;Thread用於管理腳本的執行,該類中也包含對腳本健全性的檢查。在腳本執行時,如果腳本之間存在依賴關係,那麼會將基礎的無依賴的腳本統一執行完畢,再執行依賴性的腳本。

  3)執行腳本掃描時,從nmap_main()中調用script_scan()函數。在進入script_scan()後,會標記掃描階段類型,然後進入到初始化階段返回的main()函數(來自nse_main.lua腳本中的main)中,在函數中解析具體的掃描類型。

  4)main()函數負責處理三種類型的腳本掃描:預掃描(SCRIPT_PRE_SCAN)、腳本掃描(SCRIPT_SCAN)、後掃描 (SCRIPT_POST_SCAN)。預掃描即在Nmap調用的最前面(沒有進行主機發現、端口掃描等操作)執行的腳本掃描,通常該類掃描用於準備基本的信息,例如到第三服務器查詢相關的DNS信息。而腳本掃描,是使用NSE腳本來掃描目標主機,這是最核心的掃描方式。後掃描,是整個掃描結束後,做一些善後處理的腳本,比如優化整理某些掃描。

  5)在main()函數中核心操作由run函數負責。而run()函數的本身設計用於執行所有同一級別的腳本(根據依賴關係劃分的級別),直到所有線程執行完畢才退出。run()函數中實現三個隊列:執行隊列(Running Queue)、等待隊列(Waiting Queue)、掛起隊列(Pending Queue),並管理三個隊列中線程的切換,直到全部隊列爲空或出錯而退出。

三、Nmap API

  數據傳遞

  nmap.luadoc是與nmap內部函數交互和數據結構化的API,API提供目標主機的詳細信息例如端口狀態和版本探測結果;同時API也提供與Nsock交互的接口,這樣方便我們自己寫NSE腳本與服務器交互,目前文件中共48個函數。

  在腳本引擎中,用戶可以輕鬆訪問Nmap已經瞭解的有關目標主機的信息。該數據作爲參數傳遞給NSE腳本的action方法,參數host和port是lua表,其中包含腳本執行的目標的信息。下面介紹每個表裏面所含有的變量。

  nmap使用註冊表來共享信息,每個腳本之間共享nmap.registry,每個主機還有自己的註冊表名爲host.registry;在整個掃描會話中,全局註冊表始終存在。腳本可以使用它,例如,存儲稍後將由postrule腳本顯示的值。機註冊表僅在主機被掃描時存在。它們可以用於將信息從一個腳本發送到另一個腳本。例如:1)ssh-hostkey腳本的portrule收集SSH密鑰指紋,並將其存儲在全局nmap.registry中,以便稍後可以由postrule打印。2)ssl-cert腳本收集SSL證書並將其存儲在每個主機註冊表中,以便ssl-google-cert-catalog腳本可以使用它們,而無需再次連接到服務器。nmap.registry是全局的,因此key選擇很重要;使用另一個腳本結果的腳本必須使用dependencies變量來聲明它,以確保先前的腳本首先運行。

host.ip
host.name         通過dns反向查詢的主機名,如果沒查到則爲空串
host.targetname
host.reason:      給出host處於現在這個狀態的解釋
host.reason_ttl
host.mac_addr
host.directly_connected
host.mac_addr_next_hop
host.mac_addr_src
host.interface
host.interface_mtu
host.bin_ip
host.bin_ip_src
host.times
host.traceroute

port.number
port.protocol:    有效值爲tcp、udp
port.service:    字符串表示的運行在端口號上的服務,該服務由服務探測階段探測出,如果 port.version.service_dtype字段是table,那麼Nmap基於端口號猜測服務;如果不是table,那麼版本探測階段能夠確定是什麼服務,這個字段的值被設定爲port.version.name
port.reason:      字符串解釋處於port.state狀態的原因。
port.reason_ttl
port.version:    這個字段是一個表格,包含版本探測階段返回的全部信息,包括:name;name_confidence;等,具體可參考官方文檔版本探測章節。    
port.state

參考:https://nmap.org/book/nse-api.htm

  Network I/O API

require("nmap")
-- 簡單的使用Nsock連接服務器的例子
local socket = nmap.new_socket()
socket:set_timeout(1000)
try = nmap.new_try(function() socket:close() end)
try(socket:connect(host.ip, port.number))
try(socket:send("login"))
response = try(socket:receive())
socket:close()

除此之外還有receive_bytes方法,receive_lines方法,receive_buf方法,可查看nmap.luadoc文件
除了Network的方式還有行包連接的方式:參考https://nmap.org/book/nse-api.html

四、編寫自己的腳本的建議

  編寫NSE腳本,需要根據Nmap規範編寫description,author,license,categories,rule,action字段的內容,其中主要是action字段的編寫;如果rule函數返回結果爲真,那麼執行編寫的action函數。

  The Rule

  這部分決定是否執行action函數,A prerule or a postrule 類型的腳本總是執行;在端口規則腳本里面,NSE僅給我們當前掃描端口的信息,比如一個腳本要執行,但是必須保證當前端口開啓並且113端口開啓,爲了檢測113端口是否開啓,我們使用nmap.get_port_state這個函數,如果113端口沒有被掃描,函數將返回nil。

portrule = function(host, port)
    local auth_port = { number=113, protocol="tcp" }
    local identd = nmap.get_port_state(host, auth_port)
    return identd ~= nil
        and identd.state == "open"
        and port.protocol == "tcp"
        and port.state == "open"
end

  The Action

  腳本首先連接到我們探測的端口,通過調用nmap.new_socket創建兩個套接字選項。接下來,我們定義一個錯誤處理捕獲功能,如果檢測到故障,則關閉這些套接字。此時我們可以安全地使用諸如打開,關閉,發送和接收的對象方法來在網絡套接字上操作。在這種情況下,我們調用connect來建立連接。 NSE的異常處理機制用於避免過多的錯誤處理代碼。 try用來包圍可能出錯的代碼,如果有任何問題,這將調用catch函數。如果兩個連接成功,我們構造一個查詢字符串並解析響應,最後返回解析結果。

 

action = function(host, port)
        local owner = ""
        local client_ident = nmap.new_socket()
        local client_service = nmap.new_socket()
        local catch = function()
                client_ident:close()
                client_service:close()
        end
        local try = nmap.new_try(catch)
        try(client_ident:connect(host.ip, 113))
        try(client_service:connect(host.ip, port.number))
        local localip, localport, remoteip, remoteport =
                try(client_service:get_info())
        local request = port.number .. ", " .. localport .. "\r\n"
        try(client_ident:send(request))
        owner = try(client_ident:receive_lines(1))
        if string.match(owner, "ERROR") then 
                owner = nil
        else
                owner = string.match(owner,
                        "%d+%s*,%s*%d+%s*:%s*USERID%s*:%s*.+%s*:%s*(.+)\r?\n")
        end
        try(client_ident:close())
        try(client_service:close())
        return owner
end

編寫自己的Nmap腳本

一、介紹

  在上一篇文章Nmap腳本引擎原理中我們介紹了基本的NSE知識,這篇文章介紹如何基於Nmap框架編寫簡單的NSE腳本文件,下一篇文章,Nmap腳本文件分析(AMQP協議爲例)會詳細分析Nmap自帶腳本的執行過程,以及各語句含義。

  根據上一篇文章的知識,我們知道編寫NSE腳本,主要是寫rule函數和action,rule函數返回true時,action函數執行。

二、例子

  (1)如果某個IP開放80端口則腳本掃描輸出 "This IP open 80 port!"。

  我們通過shodan搜索,得知92.62.34.104下開通了80端口。

腳本http_test.nse,放在Nmap安裝路徑的scripts文件夾下面(基於Windows,也可以放在其他下面)

portrule = function(host, port)

    return port.protocol == "tcp" 
            and port.number == 80 
            and port.state == "open"
end

-- The Action Section --
action = function(host, port)

    return "This IP ".. host.ip .." open 80 port!"
end

  輸出結果:

      (2)調用Nmap庫函數實現Rule編寫

    Nmap現在已有566種NSE腳本,爲了更容易實現判斷,對service scripts rule進行了封裝,shortport模塊已封裝了判斷函數。

-- 導入依賴模塊
local shortport = require "shortport"

-- The Rule Section --
portrule = shortport.http

-- The Action Section --
action = function(host, port)

    return "This IP ".. host.ip .." open 80 port!"
 end

三、總結

  幾點建議:

  1)我們在編寫NSE時,規則可以調用shortport提供的判斷函數,action裏面可以新建連接;要會使用host和port這兩張表,表裏麪包含了Nmap運行期間得知的所有信息;不會寫那就照着現有的566個,模仿着寫。

  2)Nmap能夠自動格式化輸出,我們只要返回結果即可,也可以通過運行選項來測試我們寫的腳本對不對,這時候要儘可能提高Nmap探測的速度,例如上面的例子可使用下面的選項探測。

  3)編寫NSE識別服務,跟我們自己使用Python、Perl、Java語言建立socket連接解析類似,只不過在NSE裏面可以使用Nmap自帶的版本探測結果,這樣方便了我們操作。

  4)熟悉HPing,Ftp,netstat等客戶端工具,一樣可以進行banner信息的提取與探測。

  5)如果能夠用上層語言調用Nmap執行,也能實現自動化探測,自動化數據分析操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章