1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
|
local ipOps = require "ipOps"
local nmap = require "nmap"
local ssh1 = require "ssh1"
local stdnse = require "stdnse"
local table = require "table"
local tableaux = require "tableaux"
description = [[
Attempts to discover multihomed systems by analysing and comparing
information collected by other scripts. The information analyzed
currently includes, SSL certificates, SSH host keys, MAC addresses,
and Netbios server names.
In order for the script to be able to analyze the data it has dependencies to
the following scripts: ssl-cert,ssh-hostkey,nbtstat.
One or more of these scripts have to be run in order to allow the duplicates
script to analyze the data.
]]
---
-- @usage
-- sudo nmap -PN -p445,443 --script duplicates,nbstat,ssl-cert <ips>
--
-- @output
-- | duplicates:
-- | ARP
-- | MAC: 01:23:45:67:89:0a
-- | 192.168.99.10
-- | 192.168.99.11
-- | Netbios
-- | Server Name: WIN2KSRV001
-- | 192.168.0.10
-- |_ 192.168.1.10
--
--
-- While the script provides basic duplicate functionality, here are some ideas
-- on improvements.
--
-- Possible additional information sources:
-- * Microsoft SQL Server instance names (Match hostname, version, instance
-- names and ports) - Reliable given several instances
-- * Oracle TNS names - Not very reliable
--
-- Possible enhancements:
-- * Compare hosts across information sources and create a global category
-- in which system duplicates are reported based on more than one source.
-- * Add a reliability index for each information source that indicates how
-- reliable the duplicate match was. This could be an index compared to
-- other information sources as well as an indicator of how good the match
-- was for a particular information source.
author = "Patrik Karlsson"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"safe"}
dependencies = {"ssl-cert", "ssh-hostkey", "nbstat"}
hostrule = function() return true end
postrule = function() return true end
local function processSSLCerts(tab)
-- Handle SSL-certificates
-- We create a new table using the SHA1 digest as index
local ssl_certs = {}
for host, v in pairs(tab) do
for port, sha1 in pairs(v) do
ssl_certs[sha1] = ssl_certs[sha1] or {}
if ( not tableaux.contains(ssl_certs[sha1], host.ip) ) then
table.insert(ssl_certs[sha1], host.ip)
end
end
end
local results = {}
for sha1, hosts in pairs(ssl_certs) do
table.sort(hosts, function(a, b) return ipOps.compare_ip(a, "lt", b) end)
if ( #hosts > 1 ) then
table.insert(results, { name = ("Certficate (%s)"):format(sha1), hosts } )
end
end
return results
end
local function processSSHKeys(tab)
local hostkeys = {}
-- create a reverse mapping key_fingerprint -> host(s)
for ip, keys in pairs(tab) do
for _, key in ipairs(keys) do
local fp = ssh1.fingerprint_hex(key.fingerprint, key.algorithm, key.bits)
if not hostkeys[fp] then
hostkeys[fp] = {}
end
-- discard duplicate IPs
if not tableaux.contains(hostkeys[fp], ip) then
table.insert(hostkeys[fp], ip)
end
end
end
-- look for hosts using the same hostkey
local results = {}
for key, hosts in pairs(hostkeys) do
if #hostkeys[key] > 1 then
table.sort(hostkeys[key], function(a, b) return ipOps.compare_ip(a, "lt", b) end)
local str = 'Key ' .. key .. ':'
table.insert( results, { name = str, hostkeys[key] } )
end
end
return results
end
local function processNBStat(tab)
local results, mac_table, name_table = {}, {}, {}
for host, v in pairs(tab) do
mac_table[v.mac] = mac_table[v.mac] or {}
if ( not(tableaux.contains(mac_table[v.mac], host.ip)) ) then
table.insert(mac_table[v.mac], host.ip)
end
name_table[v.server_name] = name_table[v.server_name] or {}
if ( not(tableaux.contains(name_table[v.server_name], host.ip)) ) then
table.insert(name_table[v.server_name], host.ip)
end
end
for mac, hosts in pairs(mac_table) do
if ( #hosts > 1 ) then
table.sort(hosts, function(a, b) return ipOps.compare_ip(a, "lt", b) end)
table.insert(results, { name = ("MAC: %s"):format(mac), hosts })
end
end
for srvname, hosts in pairs(name_table) do
if ( #hosts > 1 ) then
table.sort(hosts, function(a, b) return ipOps.compare_ip(a, "lt", b) end)
table.insert(results, { name = ("Server Name: %s"):format(srvname), hosts })
end
end
return results
end
local function processMAC(tab)
local mac
local mac_table = {}
for host in pairs(tab) do
if ( host.mac_addr ) then
mac = stdnse.format_mac(host.mac_addr)
mac_table[mac] = mac_table[mac] or {}
if ( not(tableaux.contains(mac_table[mac], host.ip)) ) then
table.insert(mac_table[mac], host.ip)
end
end
end
local results = {}
for mac, hosts in pairs(mac_table) do
if ( #hosts > 1 ) then
table.sort(hosts, function(a, b) return ipOps.compare_ip(a, "lt", b) end)
table.insert(results, { name = ("MAC: %s"):format(mac), hosts })
end
end
return results
end
postaction = function()
local handlers = {
['ssl-cert'] = { func = processSSLCerts, name = "SSL" },
['sshhostkey'] = { func = processSSHKeys, name = "SSH" },
['nbstat'] = { func = processNBStat, name = "Netbios" },
['mac'] = { func = processMAC, name = "ARP" }
}
-- temporary re-allocation code for SSH keys
for k, v in pairs(nmap.registry.sshhostkey or {}) do
nmap.registry['duplicates'] = nmap.registry['duplicates'] or {}
nmap.registry['duplicates']['sshhostkey'] = nmap.registry['duplicates']['sshhostkey'] or {}
nmap.registry['duplicates']['sshhostkey'][k] = v
end
if ( not(nmap.registry['duplicates']) ) then
return
end
local results = {}
for key, handler in pairs(handlers) do
if ( nmap.registry['duplicates'][key] ) then
local result_part = handler.func( nmap.registry['duplicates'][key] )
if ( result_part and #result_part > 0 ) then
table.insert(results, { name = handler.name, result_part } )
end
end
end
return stdnse.format_output(true, results)
end
-- we have no real action in here. In essence we move information from the
-- host based registry to the global one, so that our postrule has access to
-- it when we need it.
hostaction = function(host)
nmap.registry['duplicates'] = nmap.registry['duplicates'] or {}
for port, cert in pairs(host.registry["ssl-cert"] or {}) do
nmap.registry['duplicates']['ssl-cert'] = nmap.registry['duplicates']['ssl-cert'] or {}
nmap.registry['duplicates']['ssl-cert'][host] = nmap.registry['duplicates']['ssl-cert'][host] or {}
nmap.registry['duplicates']['ssl-cert'][host][port] = stdnse.tohex(cert:digest("sha1"), { separator = " ", group = 4 })
end
if ( host.registry['nbstat'] ) then
nmap.registry['duplicates']['nbstat'] = nmap.registry['duplicates']['nbstat'] or {}
nmap.registry['duplicates']['nbstat'][host] = host.registry['nbstat']
end
if ( host.mac_addr_src ) then
nmap.registry['duplicates']['mac'] = nmap.registry['duplicates']['mac'] or {}
nmap.registry['duplicates']['mac'][host] = true
end
return
end
local Actions = {
hostrule = hostaction,
postrule = postaction
}
-- execute the action function corresponding to the current rule
action = function(...) return Actions[SCRIPT_TYPE](...) end
|