mirror of
https://github.com/nmap/nmap.git
synced 2026-08-04 06:40:48 +00:00
Add ipv6-node-info.
This commit is contained in:
parent
ef5dfd8479
commit
57fc9a5545
3 changed files with 331 additions and 0 deletions
|
|
@ -1,5 +1,8 @@
|
|||
# Nmap Changelog ($Id$); -*-text-*-
|
||||
|
||||
o [NSE] Added ipv6-node-info, which gets hostnames and IP addresses
|
||||
with IPv6 Node Information Queries. [David]
|
||||
|
||||
o [NSE] Added the script irc-brute that performs password guessing against
|
||||
password protected IRC servers. [Patrik]
|
||||
|
||||
|
|
|
|||
327
scripts/ipv6-node-info.nse
Normal file
327
scripts/ipv6-node-info.nse
Normal file
|
|
@ -0,0 +1,327 @@
|
|||
description = [[
|
||||
Gets 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: (actually hostnames) mac-mini.local
|
||||
|
||||
categories = {"default", "discovery", "safe"}
|
||||
|
||||
author = "David Fifield"
|
||||
|
||||
require("bin")
|
||||
require("bit")
|
||||
require("dns")
|
||||
require("openssl")
|
||||
require("packet")
|
||||
|
||||
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 payload, p, flags
|
||||
local nonce
|
||||
|
||||
nonce = openssl.rand_pseudo_bytes(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
|
||||
payload = bin.pack(">SSAA", qtype, flags, nonce, dst)
|
||||
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()
|
||||
p = build_ni_query(host.bin_ip_src, host.bin_ip, QTYPE_NODEADDRESSES)
|
||||
dnet:ip_send(p)
|
||||
p = build_ni_query(host.bin_ip_src, host.bin_ip, QTYPE_NODENAME)
|
||||
dnet:ip_send(p)
|
||||
p = build_ni_query(host.bin_ip_src, host.bin_ip, QTYPE_NODEIPV4ADDRESSES)
|
||||
dnet:ip_send(p)
|
||||
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 ttl
|
||||
local names = {}
|
||||
local pos = nil
|
||||
|
||||
pos, ttl = bin.unpack(">I", data, pos)
|
||||
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
|
||||
local text
|
||||
|
||||
status, names = try_decode_nodenames(data)
|
||||
if empty(names) then
|
||||
return
|
||||
end
|
||||
text = stdnse.strjoin(", ", names)
|
||||
if not status then
|
||||
text = text .. " (parsing error)"
|
||||
end
|
||||
|
||||
return text
|
||||
end
|
||||
|
||||
-- RFC 4620, section 6.3.
|
||||
local function stringify_nodeaddresses(flags, data)
|
||||
local ttl, binaddr
|
||||
local text
|
||||
local addrs = {}
|
||||
local pos = nil
|
||||
|
||||
while true do
|
||||
pos, ttl, binaddr = bin.unpack(">IA16", data, pos)
|
||||
if not ttl then
|
||||
break
|
||||
end
|
||||
addrs[#addrs + 1] = packet.toipv6(binaddr)
|
||||
end
|
||||
if empty(addrs) then
|
||||
return
|
||||
end
|
||||
|
||||
text = stdnse.strjoin(", ", addrs)
|
||||
if bit.band(flags, 0x01) ~= 0 then
|
||||
text = text .. " (more omitted for space reasons)"
|
||||
end
|
||||
|
||||
return text
|
||||
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 text
|
||||
local addrs = {}
|
||||
local pos = nil
|
||||
|
||||
-- Check for DNS names.
|
||||
status, names = try_decode_nodenames(data .. "\0\0")
|
||||
if status then
|
||||
return "(actually hostnames) " .. stdnse.strjoin(", ", names)
|
||||
end
|
||||
|
||||
-- Okay, looks like it's really IP addresses.
|
||||
while true do
|
||||
pos, ttl, binaddr = bin.unpack(">IA4", data, pos)
|
||||
if not ttl then
|
||||
break
|
||||
end
|
||||
addrs[#addrs + 1] = packet.toip(binaddr)
|
||||
end
|
||||
if empty(addrs) then
|
||||
return
|
||||
end
|
||||
|
||||
text = stdnse.strjoin(", ", addrs)
|
||||
if bit.band(flags, 0x01) ~= 0 then
|
||||
text = text .. " (more omitted for space reasons)"
|
||||
end
|
||||
|
||||
return text
|
||||
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 p, qtype, flags, data
|
||||
local text
|
||||
|
||||
p = packet.Packet:new(buf)
|
||||
if p.icmpv6_type ~= ICMPv6_NODEINFORESP then
|
||||
return
|
||||
end
|
||||
qtype = packet.u16(p.buf, p.icmpv6_offset + 4)
|
||||
flags = packet.u16(p.buf, p.icmpv6_offset + 6)
|
||||
data = string.sub(p.buf, p.icmpv6_offset + 16 + 1)
|
||||
|
||||
if not STRINGIFY[qtype] then
|
||||
-- This is a not a qtype we sent or know about.
|
||||
stdnse.print_debug(1, "Got NI reply with unknown qtype %d from %s", qtype, packet.toipv6(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)
|
||||
local QTYPE_ORDER = {
|
||||
QTYPE_NOOP,
|
||||
QTYPE_NODENAME,
|
||||
QTYPE_NODEADDRESSES,
|
||||
QTYPE_NODEIPV4ADDRESSES,
|
||||
}
|
||||
local output
|
||||
|
||||
output = {}
|
||||
for _, qtype in ipairs(QTYPE_ORDER) do
|
||||
if results[qtype] then
|
||||
output[#output + 1] = QTYPE_STRINGS[qtype] .. ": " .. results[qtype]
|
||||
end
|
||||
end
|
||||
|
||||
return stdnse.format_output(true, 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
|
||||
|
|
@ -120,6 +120,7 @@ Entry { filename = "ip-geolocation-geoplugin.nse", categories = { "discovery", "
|
|||
Entry { filename = "ip-geolocation-ipinfodb.nse", categories = { "discovery", "external", "safe", } }
|
||||
Entry { filename = "ip-geolocation-maxmind.nse", categories = { "discovery", "external", "safe", } }
|
||||
Entry { filename = "ipidseq.nse", categories = { "discovery", "safe", } }
|
||||
Entry { filename = "ipv6-node-info.nse", categories = { "default", "discovery", "safe", } }
|
||||
Entry { filename = "irc-brute.nse", categories = { "brute", "intrusive", } }
|
||||
Entry { filename = "irc-info.nse", categories = { "default", "discovery", "safe", } }
|
||||
Entry { filename = "irc-unrealircd-backdoor.nse", categories = { "exploit", "intrusive", "malware", "vuln", } }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue