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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
|
local dns = require "dns"
local ipOps = require "ipOps"
local nmap = require "nmap"
local outlib = require "outlib"
local packet = require "packet"
local stdnse = require "stdnse"
local string = require "string"
local table = require "table"
local rand = require "rand"
description = [[
Obtains hostnames, IPv4 and IPv6 addresses through IPv6 Node Information Queries.
IPv6 Node Information Queries are defined in RFC 4620. There are three
useful types of queries:
* qtype=2: Node Name
* qtype=3: Node Addresses
* qtype=4: IPv4 Addresses
Some operating systems (Mac OS X and OpenBSD) return hostnames in
response to qtype=4, IPv4 Addresses. In this case, the hostnames are still
shown in the "IPv4 addresses" output row, but are prefixed by "(actually
hostnames)".
]]
---
-- @usage nmap -6 <target>
--
-- @output
-- | ipv6-node-info:
-- | Hostnames: mac-mini.local
-- | IPv6 addresses: fe80::a8bb:ccff:fedd:eeff, 2001:db8:1234:1234::3
-- |_ IPv4 addresses: mac-mini.local
--
-- @xmloutput
-- <elem key="Hostnames">mac-mini.local</elem>
-- <table key="IPv6 addresses">
-- <elem>fe80::a8bb:ccff:fedd:eeff</elem>
-- <elem>2001:db8:1234:1234::3</elem>
-- </table>
-- <table key="IPv4 addresses">
-- <elem>mac-mini.local</elem>
-- </table>
categories = {"default", "discovery", "safe"}
author = "David Fifield"
local ICMPv6_NODEINFOQUERY = 139
local ICMPv6_NODEINFOQUERY_IPv6ADDR = 0
local ICMPv6_NODEINFOQUERY_NAME = 1
local ICMPv6_NODEINFOQUERY_IPv4ADDR = 1
local ICMPv6_NODEINFORESP = 140
local ICMPv6_NODEINFORESP_SUCCESS = 0
local ICMPv6_NODEINFORESP_REFUSED = 1
local ICMPv6_NODEINFORESP_UNKNOWN = 2
local QTYPE_NOOP = 0
local QTYPE_NODENAME = 2
local QTYPE_NODEADDRESSES = 3
local QTYPE_NODEIPV4ADDRESSES = 4
local QTYPE_STRINGS = {
[QTYPE_NOOP] = "NOOP",
[QTYPE_NODENAME] = "Hostnames",
[QTYPE_NODEADDRESSES] = "IPv6 addresses",
[QTYPE_NODEIPV4ADDRESSES] = "IPv4 addresses",
}
local function build_ni_query(src, dst, qtype)
local flags
local nonce = rand.random_string(8)
if qtype == QTYPE_NODENAME then
flags = 0x0000
elseif qtype == QTYPE_NODEADDRESSES then
-- Set all the flags GSLCA (see RFC 4620, Figure 3).
flags = 0x003E
elseif qtype == QTYPE_NODEIPV4ADDRESSES then
-- Set the A flag (see RFC 4620, Figure 4).
flags = 0x0002
else
error("Unknown qtype " .. qtype)
end
local payload = string.pack(">I2 I2", qtype, flags) .. nonce .. dst
local p = packet.Packet:new()
p:build_icmpv6_header(ICMPv6_NODEINFOQUERY, ICMPv6_NODEINFOQUERY_IPv6ADDR, payload, src, dst)
p:build_ipv6_packet(src, dst, packet.IPPROTO_ICMPV6)
return p.buf
end
function hostrule(host)
return nmap.is_privileged() and #host.bin_ip == 16 and host.interface
end
local function open_sniffer(host)
local bpf
local s
s = nmap.new_socket()
bpf = string.format("ip6 and src host %s", host.ip)
s:pcap_open(host.interface, 1500, false, bpf)
return s
end
local function send_queries(host)
local dnet
dnet = nmap.new_dnet()
dnet:ip_open()
local p = build_ni_query(host.bin_ip_src, host.bin_ip, QTYPE_NODEADDRESSES)
dnet:ip_send(p, host)
p = build_ni_query(host.bin_ip_src, host.bin_ip, QTYPE_NODENAME)
dnet:ip_send(p, host)
p = build_ni_query(host.bin_ip_src, host.bin_ip, QTYPE_NODEIPV4ADDRESSES)
dnet:ip_send(p, host)
dnet:ip_close()
end
local function empty(t)
return not next(t)
end
-- Try to decode a Node Name reply data field. If successful, returns true and
-- a list of DNS names. In case of a parsing error, returns false and the
-- partial list of names that were parsed prior to the error.
local function try_decode_nodenames(data)
local names = {}
local ttl, pos = string.unpack(">I4", data)
if not ttl then
return false, names
end
while pos <= #data do
local name
pos, name = dns.decStr(data, pos)
if not name then
return false, names
end
-- Ignore empty names, such as those at the end.
if name ~= "" then
names[#names + 1] = name
end
end
return true, names
end
local function stringify_noop(flags, data)
return "replied"
end
-- RFC 4620, section 6.3.
local function stringify_nodename(flags, data)
local status, names
status, names = try_decode_nodenames(data)
if empty(names) then
return
end
if not status then
names[#names+1] = "(parsing error)"
end
outlib.list_sep(names)
return names
end
-- RFC 4620, section 6.3.
local function stringify_nodeaddresses(flags, data)
local ttl, binaddr
local addrs = {}
local pos = nil
while true do
ttl, binaddr, pos = string.unpack(">I4 c16", data, pos)
if not ttl then
break
end
addrs[#addrs + 1] = ipOps.str_to_ip(binaddr)
end
if empty(addrs) then
return
end
if (flags & 0x01) ~= 0 then
addrs[#addrs+1] = "(more omitted for space reasons)"
end
outlib.list_sep(addrs)
return addrs
end
-- RFC 4620, section 6.4.
-- But Mac OS X puts DNS names in here instead of IPv4 addresses, but it
-- doesn't include the two empty labels at the end as it does with a Node Name
-- response. For example, here is a Node Name reply:
-- 00 00 00 00 0e 6d 61 63 2d 6d 69 6e 69 2e 6c 6f .....mac -mini.lo
-- 63 61 6c 00 00 cal..
-- And here is a Node Addresses reply:
-- 00 00 00 00 0e 6d 61 63 2d 6d 69 6e 69 2e 6c 6f .....mac -mini.lo
-- 63 61 6c cal
local function stringify_nodeipv4addresses(flags, data)
local status, names
local ttl, binaddr
local addrs = {}
local pos = nil
-- Check for DNS names.
status, names = try_decode_nodenames(data .. "\0\0")
if status then
outlib.list_sep(names)
return names
end
-- Okay, looks like it's really IP addresses.
while true do
ttl, binaddr, pos = string.unpack(">I4 c4", data, pos)
if not ttl then
break
end
addrs[#addrs + 1] = ipOps.str_to_ip(binaddr)
end
if empty(addrs) then
return
end
if (flags & 0x01) ~= 0 then
addrs[#addrs+1] = "(more omitted for space reasons)"
end
outlib.list_sep(addrs)
return addrs
end
local STRINGIFY = {
[QTYPE_NOOP] = stringify_noop,
[QTYPE_NODENAME] = stringify_nodename,
[QTYPE_NODEADDRESSES] = stringify_nodeaddresses,
[QTYPE_NODEIPV4ADDRESSES] = stringify_nodeipv4addresses,
}
local function handle_received_packet(buf)
local text
local p = packet.Packet:new(buf)
if p.icmpv6_type ~= ICMPv6_NODEINFORESP then
return
end
local qtype, flags, pos = string.unpack(">I2I2", p.buf, p.icmpv6_offset + 4)
local data = string.sub(p.buf, pos + 8)
if not STRINGIFY[qtype] then
-- This is a not a qtype we sent or know about.
stdnse.debug1("Got NI reply with unknown qtype %d from %s", qtype, p.ip6_src)
return
end
if p.icmpv6_code == ICMPv6_NODEINFORESP_SUCCESS then
text = STRINGIFY[qtype](flags, data)
elseif p.icmpv6_code == ICMPv6_NODEINFORESP_REFUSED then
text = "refused"
elseif p.icmpv6_code == ICMPv6_NODEINFORESP_UNKNOWN then
text = string.format("target said: qtype %d is unknown", qtype)
else
text = string.format("unknown ICMPv6 code %d for qtype %d", p.icmpv6_code, qtype)
end
return qtype, text
end
local function format_results(results)
if empty(results) then
return nil
end
local QTYPE_ORDER = {
QTYPE_NOOP,
QTYPE_NODENAME,
QTYPE_NODEADDRESSES,
QTYPE_NODEIPV4ADDRESSES,
}
local output
output = stdnse.output_table()
for _, qtype in ipairs(QTYPE_ORDER) do
if results[qtype] then
output[QTYPE_STRINGS[qtype]] = results[qtype]
end
end
return output
end
function action(host)
local s
local timeout, end_time, now
local pending, results
timeout = host.times.timeout * 10
s = open_sniffer(host)
send_queries(host)
pending = {
[QTYPE_NODENAME] = true,
[QTYPE_NODEADDRESSES] = true,
[QTYPE_NODEIPV4ADDRESSES] = true,
}
results = {}
now = nmap.clock_ms()
end_time = now + timeout
repeat
local _, status, buf
s:set_timeout((end_time - now) * 1000)
status, _, _, buf = s:pcap_receive()
if status then
local qtype, text = handle_received_packet(buf)
if qtype then
results[qtype] = text
pending[qtype] = nil
end
end
now = nmap.clock_ms()
until empty(pending) or now > end_time
s:pcap_close()
return format_results(results)
end
|