Nmap腳本引擎原理
一、NSE介紹
雖然Nmap內嵌的服務於版本探測已足夠強大,但是在某些情況下我們需要多倫次的交互才能夠探測到服務器的信息,這時候就需要自己編寫NSE插件實現這個功能。NSE插件能夠完成網絡發現、復雜版本探測、脆弱性探測、簡單漏洞利用等功能。
轉載請注明出處:http://www.cnblogs.com/liun1994/
在文章 "Nmap腳本文件分析(AMQP協議為例)" 中會將版本探測,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
五、參考文獻
https://nmap.org/changelog.html#7.50 Nmap各版本更新內容
https://nmap.org/book/nse-tutorial.html NSE編寫指南
https://nmap.org/book/nse-api.html Nmap API
https://nmap.org/ Nmap官網