summaryrefslogtreecommitdiffstats
path: root/scripts/duplicates.nse
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--scripts/duplicates.nse243
1 files changed, 243 insertions, 0 deletions
diff --git a/scripts/duplicates.nse b/scripts/duplicates.nse
new file mode 100644
index 0000000..04543d4
--- /dev/null
+++ b/scripts/duplicates.nse
@@ -0,0 +1,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