1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-09 14:11:29 +00:00
Files
nmap/scripts/duplicates.nse
patrik 523dbc609a o [NSE] Added the script duplicates which attempts to determine duplicate
hosts by analyzing information collected by other scripts. [Patrik Karlsson]
2012-03-12 22:24:58 +00:00

262 lines
7.4 KiB
Lua

description = [[
The duplicates script 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 Address
- Netbios Server Name
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 http://nmap.org/book/man-legal.html"
categories = {"safe"}
dependencies = {"ssl-cert", "ssh-hostkey", "nbstat"}
require 'ipOps'
hostrule = function() return true end
postrule = function() return true end
--- check for the presence of a value in a table
--@param tab the table to search into
--@param item the searched value
--@return a boolean indicating whether the value has been found or not
local function contains(tab, item)
for _, val in pairs(tab) do
if val == item then
return true
end
end
return false
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 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 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(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(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 function format_mac(mac)
local octets = {}
for _, v in ipairs({ string.byte(mac, 1, #mac) }) do
octets[#octets + 1] = string.format("%02x", v)
end
return stdnse.strjoin(":", octets)
end
local mac
local mac_table = {}
for host in pairs(tab) do
if ( host.mac_addr ) then
mac = format_mac(host.mac_addr)
mac_table[mac] = mac_table[mac] or {}
if ( not(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