diff --git a/scripts/fcrdns.nse b/scripts/fcrdns.nse new file mode 100644 index 000000000..7c9ec0a63 --- /dev/null +++ b/scripts/fcrdns.nse @@ -0,0 +1,151 @@ +local dns = require "dns" +local ipOps = require "ipOps" +local nmap = require "nmap" +local stdnse = require "stdnse" +local string = require "string" +local table = require "table" + +description = [[ +Performs a Forward-confirmed Reverse DNS lookup and reports anomalous results. + +References: +* https://en.wikipedia.org/wiki/Forward-confirmed_reverse_DNS +]] + +--- +-- @output +-- Host script results: +-- |_fcrdns: FAIL (12.19.29.17, 12.19.20.14, 23.10.13.25) +-- +-- Host script results: +-- |_fcrdns: PASS (37.58.100.86-static.reverse.softlayer.com) +-- +-- Host script results: +-- | fcrdns: +-- | : +-- | status: fail +-- |_ reason: No PTR record +-- +-- Host script results: +-- | fcrdns: +-- | mail.example.com: +-- | status: fail +-- | reason: FCRDNS mismatch +-- | addresses: +-- | 12.19.29.17 +-- | mail.contoso.net: +-- | status: fail +-- | reason: FCRDNS mismatch +-- | addresses: +-- | 12.19.20.14 +-- |_ 23.10.13.25 +-- +--@xmloutput +-- +-- fail +-- FCRDNS mismatch +--
+-- 12.19.29.17 +--
+-- +-- +-- fail +-- FCRDNS mismatch +--
+-- 12.19.20.14 +-- 23.10.13.25 +--
+-- + +author = "Daniel Miller" + +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" + +-- not default, because user may choose -n and expect no DNS +categories = {"discovery", "safe"} + + +hostrule = function(host) + -- Every host with an IP address can be checked + return true +end + +local function keys(t) + local ret = {} + for k, _ in pairs(t) do + ret[#ret+1] = k + end + return ret +end + +if stdnse.debug == nil then + -- Backwards compatibility. + -- TODO: remove this after next release + stdnse.debug = stdnse.print_debug +end + +action = function(host) + -- Do reverse-DNS lookup of the IP + -- Can't just use host.name because some IPs have multiple PTR records + local status, rdns = dns.query(dns.reverse(host.ip), {dtype="PTR", retAll=true}) + if not status then + stdnse.debug("PTR request for %s failed: %s", host.ip, rdns) + local ret = stdnse.output_table() + ret.status = "fail" + ret.reason = "No PTR record" + return {[""]=ret}, "FAIL (No PTR record)" + end + + local str_out = nil + -- Now do forward lookup of the name(s) we got + local names = stdnse.output_table() + local fcrdns + local fail_addrs = {} + local forward_type = nmap.address_family() == "inet" and "A" or "AAAA" + local no_record_err = string.format("No %s record", forward_type) + table.sort(rdns) + for _, n in ipairs(rdns) do + local name = stdnse.output_table() + -- assume failure, we can override when/if we succeed + name.status = "fail" + name.reason = "FCRDNS mismatch" + names[n] = name + + status, fcrdns = dns.query(n, {dtype=forward_type, retAll=true}) + if not status then + stdnse.debug("%s request for %s failed: %s", forward_type, n, fcrdns) + name.reason = no_record_err + else + for _, ip in ipairs(fcrdns) do + if ipOps.compare_ip( ip, "eq", host.ip) then + name.status = "pass" + name.reason = nil + str_out = string.format("PASS (%s)", n) + end + end + name.addresses = fcrdns + if name.status == "fail" then + -- keep a list of unique addresses for short output + for _, a in ipairs(name.addresses) do + fail_addrs[a] = true + end + end + end + end + + if nmap.verbosity() > 0 then + -- use default structured output for verbosity + str_out = nil + elseif str_out == nil then + -- we failed, and need to format a short output string + fail_addrs = keys(fail_addrs) + if #fail_addrs > 0 then + table.sort(fail_addrs) + str_out = string.format("FAIL (%s)", table.concat(fail_addrs, ", ")) + else + str_out = string.format("FAIL (%s)", no_record_err) + end + end + + return names, str_out +end diff --git a/scripts/script.db b/scripts/script.db index ff1b358c0..713f14535 100644 --- a/scripts/script.db +++ b/scripts/script.db @@ -105,6 +105,7 @@ Entry { filename = "eap-info.nse", categories = { "broadcast", "safe", } } Entry { filename = "enip-info.nse", categories = { "discovery", "version", } } Entry { filename = "epmd-info.nse", categories = { "default", "discovery", "safe", } } Entry { filename = "eppc-enum-processes.nse", categories = { "discovery", "safe", } } +Entry { filename = "fcrdns.nse", categories = { "discovery", "safe", } } Entry { filename = "finger.nse", categories = { "default", "discovery", "safe", } } Entry { filename = "firewalk.nse", categories = { "discovery", "safe", } } Entry { filename = "firewall-bypass.nse", categories = { "intrusive", "vuln", } }