虽然Nmap内嵌的服务于版本探测已足够强大,可是在某些状况下咱们须要多伦次的交互才可以探测到服务器的信息,这时候就须要本身编写NSE插件实现这个功能。NSE插件可以完成网络发现、复杂版本探测、脆弱性探测、简单漏洞利用等功能。html
转载请注明出处:http://www.cnblogs.com/liun1994/前端
在文章 "Nmap脚本文件分析(AMQP协议为例)" 中会将版本探测,NSE脚本原理联系起来,具体分析Nmap探测的执行过程。
api
在文章 " 编写本身的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执行一些资源操做,先于各探测执行。
session
由上图能够看到四类脚本的运行阶段,以及他们的功能。数据结构
1)在nmap_main里面,调用init_main()进行详细的初始化过程,加载Lua标准库与Nmap扩展库,准备参数环境,加载并执行nse_main.lua文件;这个文件加载用户选择的脚本文件,执行完以后返回函数对象给init_main(),被保存到Lua注册表中。ssh
2)在nse_main.lua中,定义两个核心的类,Script和Thread,Script用于管理NSE脚本,当新的脚本被加载时,调用 Script.new建立脚本对象,该对象被保存下来在后续的扫描过程当中使用;Thread用于管理脚本的执行,该类中也包含对脚本健全性的检查。在脚本执行时,若是脚本之间存在依赖关系,那么会将基础的无依赖的脚本统一执行完毕,再执行依赖性的脚本。socket
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.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函数。
这部分决定是否执行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
脚本首先链接到咱们探测的端口,经过调用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官网