summaryrefslogtreecommitdiffstats
path: root/scripts/dns-ip6-arpa-scan.nse
blob: fb9036a5a33e7a1eb3e3f53f50767b9ca141c9b3 (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
local coroutine = require "coroutine"
local dns = require "dns"
local ipOps = require "ipOps"
local nmap = require "nmap"
local stdnse = require "stdnse"
local string = require "string"
local tab = require "tab"
local table = require "table"

description = [[
Performs a quick reverse DNS lookup of an IPv6 network using a technique
which analyzes DNS server response codes to dramatically reduce the number of queries needed to enumerate large networks.

The technique essentially works by adding an octet to a given IPv6 prefix
and resolving it. If the added octet is correct, the server will return
NOERROR, if not a NXDOMAIN result is received.

The technique is described in detail on Peter's blog:
http://7bits.nl/blog/2012/03/26/finding-v6-hosts-by-efficiently-mapping-ip6-arpa
]]

---
-- @usage
-- nmap --script dns-ip6-arpa-scan --script-args='prefix=2001:0DB8::/48'
--
-- @see dns-nsec3-enum.nse
-- @see dns-nsec-enum.nse
-- @see dns-brute.nse
-- @see dns-zone-transfer.nse
--
-- @output
-- Pre-scan script results:
-- | dns-ip6-arpa-scan:
-- | ip                                 ptr
-- | 2001:0DB8:0:0:0:0:0:2              resolver1.example.com
-- |_2001:0DB8:0:0:0:0:0:3              resolver2.example.com
--
-- @args prefix the ip6 prefix to scan
-- @args mask the ip6 mask to start scanning from

author = "Patrik Karlsson"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"intrusive", "discovery"}


local arg_prefix = stdnse.get_script_args(SCRIPT_NAME .. ".prefix")
local arg_mask = stdnse.get_script_args(SCRIPT_NAME .. ".mask")

-- Return a prefix and mask based on script arguments. First checks for "/"
-- netmask syntax; then looks for a "mask" script argument if that fails. The
-- "/" syntax wins over "mask" if both are present.
local function get_prefix_mask(arg_prefix, arg_mask)
  if not arg_prefix then
    return
  end
  local prefix, mask = string.match(arg_prefix, "^(.*)/(.*)$")
  if not mask then
    prefix, mask = arg_prefix, arg_mask
  end
  return prefix, mask
end

prerule = function()
  local prefix, mask = get_prefix_mask(arg_prefix, arg_mask)
  return prefix and mask
end

local function query_prefix(query, result)
  local condvar = nmap.condvar(result)
  local status, res = dns.query(query, { dtype='PTR' })
  if ( not(status) and res == "No Answers") then
    table.insert(result, query)
  elseif ( status ) then
    local ip = query:sub(1, -10):gsub('%.',''):reverse():gsub('(....)', '%1:'):sub(1, -2)
    ip = ipOps.bin_to_ip(ipOps.ip_to_bin(ip))
    table.insert(result, { ptr = res, query = query, ip = ip } )
  end
  condvar "signal"
end

action = function()

  local prefix, mask = get_prefix_mask(arg_prefix, arg_mask)
  local query = dns.reverse(prefix)

  -- cut the query name down to the length of the prefix
  local len = (( mask / 8 ) * 4) + #(".ip6.arpa") - 1

  local found = { query:sub(-len) }
  local threads = {}

  local i = 20

  local result
  repeat
    result = {}
    for _, f in ipairs(found) do
      for q in ("0123456789abcdef"):gmatch("(%w)") do
        local co = stdnse.new_thread(query_prefix, q .. "." .. f, result)
        threads[co] = true
      end
    end

    local condvar = nmap.condvar(result)
    repeat
      for t in pairs(threads) do
        if ( coroutine.status(t) == "dead" ) then threads[t] = nil end
      end
      if ( next(threads) ) then
        condvar "wait"
      end
    until( next(threads) == nil )

    if ( 0 == #result ) then
      return
    end

    found = result
    i = i + 1
  until( 128 == i * 2 + mask )

  table.sort(result, function(a,b) return (a.ip < b.ip) end)
  local output = tab.new(2)
  tab.addrow(output, "ip", "ptr")

  for _, item in ipairs(result) do
    tab.addrow(output, item.ip, item.ptr)
  end

  return "\n" .. tab.dump(output)
end