coolbins/system/usr/share/nmap/scripts/ipv6-node-info.nse

338 lines
8.2 KiB
Lua

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