142 lines
3.8 KiB
Lua

local dns = require "dns"
local ipOps = require "ipOps"
local nmap = require "nmap"
local stdnse = require "stdnse"
local string = require "string"
local table = require "table"
local tableaux = require "tableaux"
description = [[
Performs a Forward-confirmed Reverse DNS lookup and reports anomalous results.
References:
* https://en.wikipedia.org/wiki/Forward-confirmed_reverse_DNS
]]
---
-- @usage
-- nmap -sn -Pn --script fcrdns <target>
--
-- @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:
-- | <none>:
-- | 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
-- <table key="mail.example.com">
-- <elem key="status">fail</elem>
-- <elem key="reason">FCRDNS mismatch</elem>
-- <table key="addresses">
-- <elem>12.19.29.17</elem>
-- </table>
-- </table>
-- <table key="mail.contoso.net">
-- <elem key="status">fail</elem>
-- <elem key="reason">FCRDNS mismatch</elem>
-- <table key="addresses">
-- <elem>12.19.20.14</elem>
-- <elem>23.10.13.25</elem>
-- </table>
-- </table>
author = "Daniel Miller"
license = "Same as Nmap--See https://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
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 {["<none>"]=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 = tableaux.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