Nmap 脚本研究

时间:2022-07-23
本文章向大家介绍Nmap 脚本研究,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

>>> 为了写这一系列的文章,我用这段时间学习了 Lua语言,学习了Nmap的API,翻译了NSE库的部分源码,希望给喜欢Nmap的小伙伴带来惊喜吧!


0x01 启动过程

主机发现 -- 端口识别 -- 服务发现 -- 系统识别 -- 漏洞/认证/...

-sn ——> -sA/-sS/-sT/-sU ——> -sV ——> -O ——> --script

在服务发现过程中才会开始自动调用 NSE 脚本 详细启动过程参考

https://nmap.org/book/nse-implementation.html

0x02 脚本分类

  • auth 认证相关
  • broadcast 广播
  • brute 暴力破解
  • default 默认
  • discovery 发现相关
  • dos 拒绝服务
  • exploit 漏洞利用
  • external 外部
  • fuzzer 模糊测试
  • intrusive 侵入性
  • malware 恶意软件
  • safe 安全地
  • version 版本信息
  • vuln 有漏洞的
  • all 所有的

结果很长,这里只是选择了一小部分

0x03 脚本操作

0x031 脚本调用

Nmap的指定脚本调用使用的是 --script 参数

--script=xxx

有时候会用到 + 来进行屏蔽原本的端口服务识别的结果,可以用在修改了默认端口的一些服务上 比如 --script +ms-sql-config

以下这些格式也是非常常用

  • --script default,safe
  • --script smb-os-discovery
  • --script default,banner,/home/user/customscripts
  • --script "http-*"
  • --script "not intrusive"
  • --script "default or safe"
  • --script "default and safe"
  • --script "(default or safe or intrusive) and not http-*"

0x032 脚本参数

脚本参数使用 --script-args

--script-args 的参数是key,value形式的,被存储在 nmap.registry.args 中,可以通过 stdnse.get_script_args 来进行获取 nmap -sC --script-args 'user=foo,pass=",{}=bar",paths={/admin,/cgi-bin},xmpp-info.server_name=localhost'

使用 local server_name = stdnse.get_script_args("xmpp-info.server_name") 来进行获取值

0x033 查看脚本帮助文档

--script-help=

0x034 脚本运行追踪

--script-trace

可以通过这个参数来监控脚本运行情况

0x035 升级脚本库

--script-updatedb

0x04 脚本规则

Nmap NSE定义了四种规则

  • prerule 开始运行Nmap时调用
  • hostrule 为扫描完一个主机调用
  • portrule 为扫描完一个开放端口调用
  • postrule 为扫描结束调用

action 函数是在hostrule 或 portrule返回true 时才被调用

0x041 prerule

这个规则对应的是脚本开始运行时才调用,我们自己写一个最简单的脚本来测试一下

local stdnse = require "stdnse"
local shortport = require "shortport"
local nmap = require "nmap"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end

hostrule = function() end

portrule = function() end

postrule = function() end

action = function() end

可以看到四种规则外加 action 函数都在,在prerule 中打印输出,测试一下

可以看到,在扫描之前执行了

0x042 hostrule

这个规则对应扫描完一个主机才调用

local stdnse = require "stdnse"
local shortport = require "shortport"
local nmap = require "nmap"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end

hostrule = function()
    print("I am hostrule")
end

portrule = function() end

postrule = function() end

action = function() end

这里在之前的脚本中在 hostrule 中加入了打印输出

可以看到,成功输出

0x043 portrule

portrule 是端口规则,当扫描完一个开放端口后进行输出

local stdnse = require "stdnse"
local shortport = require "shortport"
local nmap = require "nmap"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end

hostrule = function()
    print("I am hostrule")
end

portrule = function()
    print("I am portrule")
end

postrule = function() end

action = function() end

测试一下

可以看到成功输出,在这里我们只扫描了一个 443 端口,所以只输出了一次,那么我多扫描几个呢?

可以看到,扫描了三个端口,其中有两个是开放的,所以打印了端口规则两次。不过看到这里大家肯定会有些奇怪:

一个端口的时候打印顺序是 先主机规则再端口规则

两个端口的时候打印顺序是 先端口规则,再主机规则

为了验证我的猜想,我多次执行

可以看到,打印顺序不是固定的,所以应该是多线程打印的事

0x044 postrule

在脚本执行完毕前调用此规则

local stdnse = require "stdnse"
local shortport = require "shortport"
local nmap = require "nmap"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end

hostrule = function()
    print("I am hostrule")
end

portrule = function()
    print("I am portrule")
end

postrule = function()
    print("I am postrule")
end

action = function() end

测试一下

可以看到成功执行此规则

0x045 action

这个函数只有在 hostrule 或 portrule 返回 true 时才执行

local stdnse = require "stdnse"
local shortport = require "shortport"
local nmap = require "nmap"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end

hostrule = function()
    print("I am hostrule")
    return true
end

portrule = function()
    print("I am portrule")
end

postrule = function()
    print("I am postrule")
end

action = function()
    print('I am action')
end

测试一下

我们让 hostrule 和 portrule 都返回 true 试试

local stdnse = require "stdnse"
local shortport = require "shortport"
local nmap = require "nmap"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end

hostrule = function()
    print("I am hostrule")
    return true
end

portrule = function()
    print("I am portrule")
    return true
end

postrule = function()
    print("I am postrule")
end

action = function()
    print('I am action')
end

测试一下

可以看到,action函数执行了三遍

0x05 host

host 是一个对象(表),其中有非常多的属性,非常重要

  • host.os
  • host.ip
  • host.name
  • host.targetname
  • host.reason
  • host.resion_ttl
  • host.directly_connected
  • host.mac_addr
  • 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
  • host.os_fp
local stdnse = require "stdnse"
local shortport = require "shortport"
local nmap = require "nmap"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end

hostrule = function()
    return true
end


action = function(host, port)
    print('I am action')
    local output = stdnse.output_table()
    output.host_os = host.host or "null"
    output.host_ip = host.ip or "null"
    output.host_name = host.name or "null"
    output.host_targetname = host.targetname or "null"
    output.host_reason = host.reason or "null"
    output.host_resion_ttl = host.resion_ttl or "null"
    output.host_directly_connected = host.directly_connected or "null"
    output.host_mac_addr = host.mac_addr or "null"
    output.host_mac_addr_next_hop = host.mac_addr_next_hop or "null"
    output.host_mac_addr_src = host.mac_addr_src or "null"
    output.host_interface = host.interface or "null"
    output.host_interface_mtu = host.interface_mtu or "null"
    output.host_bin_ip = host.bin_ip or "null"
    output.host_bin_ip_src = host.bin_ip_src or "null"
    output.host_times = host.times or "null"
    output.host_traceroute = host.traceroute or "null"
    output.host_os_fp = host.os_fp or "null"
    return output
end

测试一下

经过几个目标的测试,可以得到以下结论

0x051 host.os

操作系统匹配表数组, OS匹配项由易于理解的名称和一系列OS类组成。每个OS类包括一个供应商,OS系列,OS生成,设备类型以及该类的CPE条目数组,具体结构如下:

host.os = {
  {
    name = <string>,
    classes = {
      {
        vendor = <string>,
        osfamily = <string>,
        osgen = <string>,
        type = <string>,
        cpe = {
          "cpe:/<...>",
          [More CPE]
        }
      },
      [More classes]
    },
  },
  [More OS matches]
}

加入匹配到的目标信息为

Fingerprint Linux 2.6.32 - 3.2
Class Linux | Linux | 2.6.X | general purpose
CPE cpe:/o:linux:linux_kernel:2.6
Class Linux | Linux | 3.X | general purpose
CPE cpe:/o:linux:linux_kernel:3

那么在 host.os 中存储如下:

host.os = {
  {
    name = "Linux 2.6.32 - 3.2",
    classes = {
      {
        vendor = "Linux",
        osfamily = "Linux",
        osgen = "2.6.X",
        type = "general purpose",
        cpe = { "cpe:/o:linux:linux_kernel:2.6" }
      },
      {
        vendor = "Linux",
        osfamily = "Linux",
        osgen = "3.X",
        type = "general purpose",
        cpe = { "cpe:/o:linux:linux_kernel:3" }
      }
    },
  }
}

0x052 host.ip

目标主机的IP地址,如果给定的目标是域名,则会返回域名解析后的IP

0x053 host.name

反向DNS查询后的域名信息,如果查询不到,则返回空

0x054 host.targetname

目标域名,如果目标是域名形式,则为这个域名;如果给定的是IP,则为空

0x055 host.reason

判断依据,如果是正常返回的就是 echo-reply ; 如果是被防火墙拦截等则返回 user-set;如果在内网返回

0x056 host.resion_ttl

包含响应数据包的TTL值,用于确定目标主机到达时的状态。此响应数据包是也用于设置host.reason的数据包

0x057 host.directly_connected

判断是否与Nmap运行的主机在同一个网段,是一个布尔值

0x058 host.mac_addr

目标主机的mac地址, 一般只有目标主机在内网才会有这个值,而且只有做原始网络帧扫描时候才会有结果

0x059 host.mac_addr_next_hop

到主机的路由中第一跳的MAC地址,如果不可用,则为nil

0x0510 host.mac_addr_src

我们自己的MAC地址,该地址用于连接到主机(我们的网卡的地址,或(与--spoof-mac一起使用的)欺骗地址)

0x0511 host.interface

我们发送数据包的接口名称

0x0512 host.interface_mtu

host.interface的MTU(最大传输单位),如果未知,则为0

0x0513 host.bin_ip

目标主机的IP地址,为4字节(IPv4)或16字节(IPv6)字符串

0x0514 host.bin_ip_src

我们的主机的IP地址,为4字节(IPv4)或16字节(IPv6)字符串

0x0515 host.times

该表包含主机的Nmap时序数据。它的键是srtt(平滑的往返时间),rttvar(往返时间变化)和timeout(探测超时),所有这些都以浮点秒为单位

0x0516 host.traceroute

这是一个跟踪路由跃点数组,在使用--traceroute选项时出现。每个条目都是一个主机表,其中包含字段名称,ip和srtt(往返时间)。给定条目在表中的位置,该条目的TTL是隐式的。空表表示超时跳

0x0517 host.os_fp

如果执行了OS检测,则这是一个字符串,其中包含主机的OS指纹


很多字段在演示过程中都没有得到体现,大家在测试端口,服务,系统识别等方面可以进行多多测试,来确定每一个字段的值的存储形式

0x06 port

port 也是一个对象(表),其中有非常多的属性,非常重要

  • port.number
  • port.protocol
  • port.service
  • port.reason
  • port.reason_ttl
  • port.version
    • name
    • name_confidence
    • product.version,extrainfo,hostname,ostype,devicetype
    • service_tunnel
    • service_fp
    • service_dtype
    • cpe
  • port.state
local stdnse = require "stdnse"
local shortport = require "shortport"
local nmap = require "nmap"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end

hostrule = function() end

portrule = function()
    return true
end

action = function(host, port)
    print('I am action')
    local output = stdnse.output_table()
    output.port_number = port.number or "null"
    output.port_protocol = port.protocol or "null"
    output.port_service = port.service or "null"
    output.port_reason = port.reason or "null"
    output.port_reason_ttl = port.reason_ttl or "null"
    output.port_version = port.version or "null"
    output.port_state = port.state or "null"
    return output
end

测试一下

0x061 port.number

端口号

0x062 port.protocol

协议类型

0x063 port.service

端口服务类型

0x064 port.reason

判断依据

0x065 port.reson_ttl

包含响应数据包的TTL值,用于确定目标端口到达时的状态。此响应数据包是也用于设置port.reason的数据包

0x066 port.version

此项是一个表,其中包含Nmap版本扫描引擎检索到的信息。即使未执行版本扫描,Nmap仍可能检索某些值(例如服务名称,服务类型可信度和与RPC相关的值)。未确定的值默认为nil

  • name 服务名
  • name_confidence 评估Nmap对名称准确性的置信度,从1(最低置信度)到10。如果port.version.service_dtype为“ table”,则为3
  • product.version,extrainfo,hostname,ostype,devicetype 版本相关的 5 个变量
  • service_tunnel 根据Nmap是否使用SSL隧道检测服务,包含字符串“ none”或“ ssl”。
  • service_fp 服务指纹信息
  • service_dtype 根据Nmap是否从nmap-services文件或服务探测器匹配推导出port.version.name来包含字符串“ table”或“ probed”
  • cpe 检测到的服务的CPE代码列表。如官方CPE规范中所述,这些字符串都以cpe:/前缀开头

0x067 port.state

端口状态

0x07 registry

脚本可以通过将值存储在注册表中来共享信息,该注册表是可以被所有脚本访问的特殊表。有一个名为 nmap.registry 的全局注册表,由所有脚本共享。每个主机还具有自己的注册表,称为 host.registry,其中host是传递给脚本的主机表。注册表中的信息不存储在Nmap执行之间

全局注册表会在整个扫描会话中持续存在。脚本可以使用它来存储例如稍后将由脚本规则脚本显示的值。另一方面,每个主机的注册表仅在扫描主机时存在,它们可用于将信息从一个脚本发送到在同一主机上运行的另一个脚本。如果可能,请使用每个主机的注册表。这不仅使您不必在各个主机之间使键名唯一,而且还使注册表不再需要时可以回收注册表所使用的内存

使用另一个脚本结果的脚本必须使用 dependencies 变量声明它,以确保较早的脚本先运行(至于dependencies 变量我们稍后会介绍)

0x08 环境变量

  • SCRIPT_PATH 脚本路径
  • SCRIPT_NAME 脚本名称
  • SCRIPT_TYPE "脚本执行类型"
    • 这个脚本主要是debug的时候进行使用,这个脚本可以返回 prerule、hostrule、portrule、postrule 四个规则中哪一个被执行了
local stdnse = require "stdnse"
local shortport = require "shortport"
local nmap = require "nmap"

description = [[
This is a test for http.lua's functions
]]

author = "test94"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"default"}

prerule = function()
    print("functest running")
end

hostrule = function()
    return true
end


action = function(host, port)
    print('I am action')
    local output = stdnse.output_table()
    output.SCRIPT_NAME = SCRIPT_NAME
    output.SCRIPT_TYPE = SCRIPT_TYPE
    output.SCRIPT_PATH = SCRIPT_PATH
    return output
end

测试一下输出

0x09 NSE脚本编写规范

NSE 脚本由很多部分组成,这样才能够保证脚本顺利运行,当然很多部分可以缺失,根据实际需要更改吧

先看一个例子吧 ,以 http-backup-finder.nse 为例

local coroutine = require "coroutine"
local http = require "http"
local httpspider = require "httpspider"
local shortport = require "shortport"
local stdnse = require "stdnse"
local table = require "table"
local url = require "url"

description = [[
Spiders a website and attempts to identify backup copies of discovered files.
It does so by requesting a number of different combinations of the filename (eg. index.bak, index.html~, copy of index.html).
]]

---
-- @usage
-- nmap --script=http-backup-finder <target>
--
-- @output
-- PORT   STATE SERVICE REASON
-- 80/tcp open  http    syn-ack
-- | http-backup-finder:
-- | Spidering limited to: maxdepth=3; maxpagecount=20; withindomain=example.com
-- |   http://example.com/index.bak
-- |   http://example.com/login.php~
-- |   http://example.com/index.php~
-- |_  http://example.com/help.bak
--
-- @args http-backup-finder.maxdepth the maximum amount of directories beneath
--       the initial url to spider. A negative value disables the limit.
--       (default: 3)
-- @args http-backup-finder.maxpagecount the maximum amount of pages to visit.
--       A negative value disables the limit (default: 20)
-- @args http-backup-finder.url the url to start spidering. This is a URL
--       relative to the scanned host eg. /default.html (default: /)
-- @args http-backup-finder.withinhost only spider URLs within the same host.
--       (default: true)
-- @args http-backup-finder.withindomain only spider URLs within the same
--       domain. This widens the scope from <code>withinhost</code> and can
--       not be used in combination. (default: false)
--

author = "Patrik Karlsson"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"}


portrule = shortport.http

local function backupNames(filename)
  local function createBackupNames()
    local dir = filename:match("^(.*/)") or ""
    local basename, suffix = filename:match("([^/]*)%.(.*)$")

    local backup_names = {}
    if basename then
      table.insert(backup_names, "{basename}.bak") -- generic bak file
    end
    if basename and suffix then
      table.insert(backup_names, "{basename}.{suffix}~") -- emacs
      table.insert(backup_names, "{basename} copy.{suffix}") -- mac copy
      table.insert(backup_names, "Copy of {basename}.{suffix}") -- windows copy
      table.insert(backup_names, "Copy (2) of {basename}.{suffix}") -- windows second copy
      table.insert(backup_names, "{basename}.{suffix}.1") -- generic backup
      table.insert(backup_names, "{basename}.{suffix}.~1~") -- bzr --revert residue

    end

    local replace_patterns = {
      ["{filename}"] = filename,
      ["{basename}"] = basename,
      ["{suffix}"] = suffix,
    }

    for _, name in ipairs(backup_names) do
      local backup_name = name
      for p, v in pairs(replace_patterns) do
        backup_name = backup_name:gsub(p,v)
      end
      coroutine.yield(dir .. backup_name)
    end
  end
  return coroutine.wrap(createBackupNames)
end

action = function(host, port)
  local crawler = httpspider.Crawler:new(host, port, nil, { scriptname = SCRIPT_NAME } )
  crawler:set_timeout(10000)

  -- Identify servers that answer 200 to invalid HTTP requests and exit as these would invalidate the tests
  local status_404, result_404, known_404 = http.identify_404(host,port)
  if ( status_404 and result_404 == 200 ) then
    stdnse.debug1("Exiting due to ambiguous response from web server on %s:%s. All URIs return status 200.", host.ip, port.number)
    return nil
  end

  -- Check if we can use HEAD requests
  local use_head = http.can_use_head(host, port, result_404)

  local backups = {}
  while(true) do
    local status, r = crawler:crawl()
    -- if the crawler fails it can be due to a number of different reasons
    -- most of them are "legitimate" and should not be reason to abort
    if ( not(status) ) then
      if ( r.err ) then
        return stdnse.format_output(false, r.reason)
      else
        break
      end
    end

    -- parse the returned url
    local parsed = url.parse(tostring(r.url))

    -- handle case where only hostname was provided
    if ( parsed.path == nil ) then
      parsed.path = '/'
    end

    -- only pursue links that have something looking as a file
    if ( parsed.path:match(".*%.*.$") ) then
      -- iterate over possible backup files
      for link in backupNames(parsed.path) do
        local host = parsed.host
        local port = parsed.port or url.get_default_port(parsed.scheme)

        -- the url.escape doesn't work here as it encodes / to %2F
        -- which results in 400 bad request, so we simple do a space
        -- replacement instead.
        local escaped_link = link:gsub(" ", "%%20")

        local response
        if(use_head) then
          response = http.head(host, port, escaped_link, {redirect_ok=false})
        else
          response = http.get(host, port, escaped_link, {redirect_ok=false})
        end

        if http.page_exists(response, result_404, known_404, escaped_link, false) then
          if ( not(parsed.port) ) then
            table.insert(backups,
              ("%s://%s%s"):format(parsed.scheme, host, link))
          else
            table.insert(backups,
              ("%s://%s:%d%s"):format(parsed.scheme, host, port, link))
          end
        end
      end
    end
  end

  if ( #backups > 0 ) then
    backups.name = crawler:getLimitations()
    return stdnse.format_output(true, backups)
  end
end

这么长看起来不直观,下面我们来分解分析一下

0x091 导包

local coroutine = require "coroutine"
local http = require "http"
local httpspider = require "httpspider"
local shortport = require "shortport"
local stdnse = require "stdnse"
local table = require "table"
local url = require "url"

0x092 description

description = [[
Spiders a website and attempts to identify backup copies of discovered files.
It does so by requesting a number of different combinations of the filename (eg. index.bak, index.html~, copy of index.html).
]]

这一部分是描述信息,基本上都是以 [[ some words ]] 这种形式的,所以尽量大家也不要变成其他形式的注释

0x093 NSEDoc

---
-- @usage
-- nmap --script=http-backup-finder <target>
--
-- @output
-- PORT   STATE SERVICE REASON
-- 80/tcp open  http    syn-ack
-- | http-backup-finder:
-- | Spidering limited to: maxdepth=3; maxpagecount=20; withindomain=example.com
-- |   http://example.com/index.bak
-- |   http://example.com/login.php~
-- |   http://example.com/index.php~
-- |_  http://example.com/help.bak
--
-- @args http-backup-finder.maxdepth the maximum amount of directories beneath
--       the initial url to spider. A negative value disables the limit.
--       (default: 3)
-- @args http-backup-finder.maxpagecount the maximum amount of pages to visit.
--       A negative value disables the limit (default: 20)
-- @args http-backup-finder.url the url to start spidering. This is a URL
--       relative to the scanned host eg. /default.html (default: /)
-- @args http-backup-finder.withinhost only spider URLs within the same host.
--       (default: true)
-- @args http-backup-finder.withindomain only spider URLs within the same
--       domain. This widens the scope from <code>withinhost</code> and can
--       not be used in combination. (default: false)
--

这一部分一般包括 @usage、@output、@args等,其实也是写不写都行,但是作为一个半吊子的coder,我还是经常喜欢写的,规范咱们就遵守嘛

0x094 author

author = "Patrik Karlsson"

作者信息

0x095 license

license = "Same as Nmap--See https://nmap.org/book/man-legal.html"

这个所有的脚本都是一样的

0x096 categories

categories = {"discovery", "safe"}

目录表,这个脚本属于哪些个目录,就在这里填写哪些就好了

0x097 dependencies

dependencies = {"smb-brute"}

依赖性区域,是一个列表,在这个列表中列出来的脚本如果在本次执行中也进行了调用,那么先调用列表中的文件,之后再执行我们的脚本 其中我们的脚本可以调用列表中脚本的执行结果

一般smb的脚本都会先调用smb-brute (5.10beta2版本后,依赖区域不再使用,而是手动使用 runlevel 区域)


其实在接下来的部分中可能穿插着用户自定义的函数与代码

0x098 rules

  • prerule()
  • hostrule(host)
  • portrule(host, port)
  • postrule()

0x099 action()

当 hostrule 和 portrule 返回 true 时才会执行

0x010 NSEDoc

为了让所有开发者都能看懂这些脚本,一个NSEDoc是必要的,所以单拿出来讲解

下面是 stdnse.print_debug() 函数文档

---
-- Prints a formatted debug message if the current verbosity level is greater
-- than or equal to a given level.
--
-- This is a convenience wrapper around
-- <code>nmap.log_write</code>. The first optional numeric
-- argument, <code>level</code>, is used as the debugging level necessary
-- to print the message (it defaults to 1 if omitted). All remaining arguments
-- are processed with Lua's <code>string.format</code> function.
-- @param level Optional debugging level.
-- @param fmt Format string.
-- @param ... Arguments to format.

0x0101 规范:

  • 文档注释以三个 - 开始,之后就是针对一下代码的说明
  • 第一段应该是一个简短的总结,接下来的段落进行详细说明
  • 特殊的标签要以 @ 开头来划分出文档的一部分
  • 比如 @param 是用来描述每个功能参数的标签 之间的会呈现出一个等宽字体,这个应该被用在变量,函数名以及多行注释
  • 如果一个序列行从字符 * 开始,他们将作为项目符号列表呈现,每个列表项都必须完全在同一个物理行

推荐每一个module和每一个每一个table都有一个注释文件 下面是 comm module的一个注释

---
-- Common communication functions for network discovery tasks like
-- banner grabbing and data exchange.
--
-- These functions may be passed a table of options, but it's not required. The
-- keys for the options table are <code>"bytes"</code>, <code>"lines"</code>,
-- <code>"proto"</code>, and <code>"timeout"</code>. <code>"bytes"</code> sets
-- a minimum number of bytes to read. <code>"lines"</code> does the same for
-- lines. <code>"proto"</code> sets the protocol to communicate with,
-- defaulting to <code>"tcp"</code> if not provided. <code>"timeout"</code>
-- sets the socket timeout (see the socket function <code>set_timeout</code>
-- for details).
--
-- @author Kris Katterjohn 04/2008
-- @copyright Same as Nmap--See https://nmap.org/book/man-legal.html

description = [[
Maps IP addresses to autonomous system (AS) numbers.

The script works by sending DNS TXT queries to a DNS server which in
turn queries a third-party service provided by Team Cymru
(team-cymru.org) using an in-addr.arpa style zone set up especially for
use by Nmap. The responses to these queries contain both Origin and Peer
ASNs and their descriptions, displayed along with the BGP Prefix and
Country Code. The script caches results to reduce the number of queries
and should perform a single query for all scanned targets in a BGP
Prefix present in Team Cymru's database.

Be aware that any targets against which this script is run will be sent
to and potentially recorded by one or more DNS servers and Team Cymru.
In addition your IP address will be sent along with the ASN to a DNS
server (your default DNS server, or whichever one you specified with the
<code>dns</code> script argument).
]]

---
-- @usage
-- nmap --script asn-query [--script-args dns=<DNS server>] <target>
-- @args dns The address of a recursive nameserver to use (optional).
-- @output
-- Host script results:
-- |  asn-query:
-- |  BGP: 64.13.128.0/21 | Country: US
-- |    Origin AS: 10565 SVCOLO-AS - Silicon Valley Colocation, Inc.
-- |      Peer AS: 3561 6461
-- |  BGP: 64.13.128.0/18 | Country: US
-- |    Origin AS: 10565 SVCOLO-AS - Silicon Valley Colocation, Inc.
-- |_     Peer AS: 174 2914 6461

author = "jah, Michael"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"discovery", "external", "safe"}

0x0102 NSE 文档标签如下

  • @param 一个函数的每一个参数都应该有一个描述
  • @see 添加一个交叉引用到另一个函数或表
  • @return 描述一个函数返回值,可能多次用于多个返回值
  • @usage 描述一个函数的使用方法或者nmap命令等
  • @name 函数或者表的名字,一般没啥必要
  • @class 定义类,方法,或者模块,通常自动推断出来
  • @field 一般在表中用来描述指定字段的值
  • @args --script-args option 中定义的参数,一般@args后面跟的第一个参数为 参数名称,之后是这个参数的描述
  • @output 一个脚本的输出样式
  • @xmloutput XML格式化输出的一个样式
  • @author 作者
  • @copyright 著作权声明