summaryrefslogtreecommitdiffstats
path: root/scripts/fcrdns.nse
blob: e2a51b1783b74ec417d6c012e968b80f823a622d (plain)
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
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