Nmap进行漏洞扫描,编写扩展
0x00 资产扫描、汇总、实时监控
资产扫描能够有利于企业内部查看终端、监控终端、对终端进行安全加固。周期性的扫描能有效快速修补漏洞、降低办公网络风险。
如何进行汇总、实时监控?
在我们要进行汇总的时候,有如下几个可以考虑的方案。
- Excel
- Text
- Database / SQL
PDF && Excel && Text 都不适合实时View
Database / SQL 有利于生成数据汇总、图表,并且可移植性很高。
Database -> Web -> Excel/PDF …. 可行性都变得高了起来
实时监控采用任务调度,数据库采用IO效率高的NO SQL产品,详细信息采用普通的数据库:MySQL、SQL Server、Oracel…
0X01 解决方案
Nmap简介、目录结构、扫描流程、Nse Engine
0X02 简介
Nmap (“Network Mapper(网络映射器)”) 是一款开放源代码的 网络探测和安全审核的工具。它的设计目标是快速地扫描大型网络,当然用它扫描单个 主机也没有问题。Nmap以新颖的方式使用原始IP报文来发现网络上有哪些主机,那些 主机提供什么服务(应用程序名和版本),那些服务运行在什么操作系统(包括版本信息), 它们使用什么类型的报文过滤器/防火墙,以及一堆其它功能。虽然Nmap通常用于安全审核, 许多系统管理员和网络管理员也用它来做一些日常的工作,比如查看整个网络的信息, 管理服务升级计划,以及监视主机和服务的运行。
目录结构
文件名称 | 文件说明 |
nmap.dtd | Nmap输出的XML格式内部变量的定义 |
nmap-mac-prefixes | 是Nmap针对不同终端的MAC地址所收集的指纹(常用于内网扫描) |
nmap-os-db | Nmap针对不同终端的操作系统返回的数据包特征所收集的指纹 |
nmap-payloads | 是Nmap在扫描时将payload向扫描目标发送的数据 |
nmap-protocols | Nmap 用来存储目标端口对应服务描述的db文件 |
nmap-rpc | Nmap在扫描的时候调用RPC进行服务发现的db文件 |
nmap-service-probes | Nmap针对响应数据包内容进行正则匹配从而判断服务的db文件 |
nmap-services | Nmap存储一个TCP/UDP服务的db文件 |
nmap-xsl | Nmap导出xml文件的模板 |
nselib | Nmap的脚本引擎扩展库 |
nse_main.lua | 在调用任何Nmap脚本都会提前自动调用的预处理Lua脚本 |
Scripts | Nmap的脚本扩展 |
扫描流程 V1
扫描流程 V2
Nse Engine(Nmap 脚本引擎)
Nmap Nse 脚本引擎用于针对发现的OS、主机、端口进行不同的操作,例如:Fuzz测试、漏洞发现、漏洞利用等。这对Nmap又增添了一大亮点,所以说Nmap不只是一个扫描工具,在黑客的手中,更是一款爱不释手的渗透工具。
Nse Engine的执行流程
0X03 一个简单的扩展打开世界
https://nmap.org/book/nse-api.html
Nmap扩展主要由以下几个变量构成。编码方式:变量绑定函数
变量名称 | 函数执行顺序 |
prerule() | 最先执行 |
hostrule(host) | 第二步执行 |
portrule(host,port) | 第二步执行 |
postrule() | 最后一步执行 |
顺序为:Prerule -> Hostrule or Portrule -> Action -> Postrule
当 Hostrule
或者 Portrule
的绑定函数返回true
的时候,都会执行一次Action
的绑定函数。
入库
获取参数->连接数据库 -> 采用不同条件判断期望获取到的值->执行SQL
获取参数:stdnse lib [stdnse.get_script_args()]
连接数据库:mysql lib
获取socket : nmap socket lib
socket = nmap.new_socket() – 获得一个新的socket套接字
socket:set_timeout(1000) – 设置超时时间
socket:connect(host_ip,port_number) – 连接目标
socket:close() – 关闭套接字连接
我自己根据API封装了一个执行MySQL Query的扩展:
local mysql = require "mysql"
local nmap = require "nmap"
local stdnse = require "stdnse"
description =[[
This is a Lua script that performs the MySQL statement.
]]
author = "China CoraLab payloads"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"external"}
socket = nmap.new_socket()
socket:set_timeout(1000)
try = nmap.new_try(function() socket:close() end)
optionsConfig = {
username = "root",
password = "root",
database = "test",
sqlQuery = "SELECT MD5('admin'),",
host = "127.0.0.1",
port = 3306
}
optionsConfig.username = stdnse.get_script_args(SCRIPT_NAME .. ".username") or optionsConfig.username
optionsConfig.password = stdnse.get_script_args(SCRIPT_NAME .. ".password") or optionsConfig.password
optionsConfig.database = stdnse.get_script_args(SCRIPT_NAME .. ".database") or optionsConfig.database
optionsConfig.host = stdnse.get_script_args(SCRIPT_NAME .. ".host") or optionsConfig.host
optionsConfig.port = stdnse.get_script_args(SCRIPT_NAME .. ".port") or optionsConfig.port
optionsConfig.sqlQuery = stdnse.get_script_args(SCRIPT_NAME .. ".sql") or optionsConfig.sqlQuery
function mysqlLogin(socket, username, password)
local status, response = mysql.receiveGreeting( socket )
if ( not(status) ) then
return response
end
return mysql.loginRequest( socket, { authversion = "post41", charset = response.charset }, username, password, response.salt )
end
portrule = function (host,port)
return true
end
action = function(host,port)
if(socket:connect(optionsConfig.host,optionsConfig.port))
then
local status,response = mysqlLogin(socket,optionsConfig.username,optionsConfig.password)
if(status)
then
local status, rs = mysql.sqlQuery( socket, optionsConfig.sqlQuery )
socket:close()
local result = mysql.formatResultset(rs, { noheaders = true })
stdnse.debug(string.format("[*]Query is %s | Result is %s",optionsConfig.sqlQuery,result))
return stdnse.format_output(true,result)
end
else
print("Connect to mysql Failed !!!")
return nil
end
end
stdnse.get_script_args
是用于获取参数的函数,对应Nmap的–script-args参数。
遇到的问题
在Nse扩展库中,无法与外部地址(除去扫描范围以外的目标)进行Socket连接。
因为大部分函数都接收绑定函数中的Host与Port参数,他们是一个table数据类型。
于是我翻阅API扩展,发现了一个方法可以直接获取一个IP+端口的套接字:
socket = nmap.new_socket()
socket:set_timeout(1000)
socket:connect(HOST_ADDRESS,PORT_NUMBER)
如上方法我们可以直接传递IP地址与端口号就可以获得套接字了。
0X04 扩展结构设计
nse_main.lua 是声明全局变量、装载script db的预处理脚本,我们可以在其中将连接数据库的行为载入,然后用portrule函数进行数据库的读写。
nmap –script=scan_save_database 127.0.0.1
执行流程:
- nse_main.lua 装载
- 寻找 scan_save_database 脚本
- 主机发现
- 端口扫描
- 版本侦测
- 系统指纹侦测
- ….
- 执行prerule函数
- 执行hostrule函数(如果返回true,执行action)
- 执行portrule函数 (如果返回true,执行action)
- 执行postrule函数
主要是在扩展脚本中的portrule or hostrule 过滤我们的想要的数据,最后再action中对数据库进行读写。
下面演示一下获取开启80端口的所有主机:
local mysql = require "mysql"
local nmap = require "nmap"
local stdnse = require "stdnse"
local shortport = require "shortport"
description =[[
This is a Lua script that performs the MySQL statement.
]]
author = "China CoraLab payloads"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"external"}
socket = nmap.new_socket()
socket:set_timeout(1000)
try = nmap.new_try(function() socket:close() end)
optionsConfig = {
username = "root",
password = "root",
database = "test",
sqlQuery = "INSERT INTO scan_table VALUES (NULL,'%s','%s','%s')",
host = "127.0.0.1",
port = 3306
}
optionsConfig.username = stdnse.get_script_args(SCRIPT_NAME .. ".username") or optionsConfig.username
optionsConfig.password = stdnse.get_script_args(SCRIPT_NAME .. ".password") or optionsConfig.password
optionsConfig.database = stdnse.get_script_args(SCRIPT_NAME .. ".database") or optionsConfig.database
optionsConfig.host = stdnse.get_script_args(SCRIPT_NAME .. ".host") or optionsConfig.host
optionsConfig.port = stdnse.get_script_args(SCRIPT_NAME .. ".port") or optionsConfig.port
optionsConfig.sqlQuery = stdnse.get_script_args(SCRIPT_NAME .. ".sql") or optionsConfig.sqlQuery
function mysqlLogin(socket, username, password)
local status, response = mysql.receiveGreeting( socket )
if ( not(status) ) then
return response
end
return mysql.loginRequest( socket, { authversion = "post41", charset = response.charset }, username, password, response.salt )
end
portrule=function(host,port)
if(port.number==80) then
print("[*] Scan this host open 80 port -- " .. host.ip)
return true
end
end
--
-- portrule = shortport.portnumber({80,443,8080},"tcp",{"open","open|filtered"})
-- portrule = shortport.port_or_service({80,443,8080},"http","tcp",{"open","open|filtered"})
-- portrule = service("http","tcp",{"open"})
--
action=function(host,port)
-- load mysql lib
-- get scan info
optionsConfig.sqlQuery = string.format("INSERT INTO scan_table VALUES (NULL,'%s','%s','%s')",host.ip,port.number,port.service)
if(socket:connect(optionsConfig.host,optionsConfig.port))
then
local status,response = mysqlLogin(socket,optionsConfig.username,optionsConfig.password)
if(status)
then
local status, rs = mysql.sqlQuery( socket, optionsConfig.sqlQuery )
socket:close()
local result = mysql.formatResultset(rs, { noheaders = true })
stdnse.debug(string.format("Query is %s | Result is %s",optionsConfig.sqlQuery,result))
return stdnse.format_output(true,host.ip)
end
else
print("Connect to mysql Failed !!!")
return nil
end
end
执行扫描:nmap --script=scan_db --script-args username=root 192.168.3.183 -d -p 80
执行过程:
不开启debug…
结果:
0X05 More ideas
更多插件…
实用的函数手册…
代码规范
共同维护
有效的任务调度
0x06 共同探讨
倾旋 Email:[email protected]