1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-17 05:09:00 +00:00

Fix a few bugs in targets-ipv6-multicast-mld. http://seclists.org/nmap-dev/2015/q2/250

This commit is contained in:
dmiller
2015-12-16 17:07:40 +00:00
parent c199828a49
commit 0f99596555
2 changed files with 222 additions and 81 deletions

181
nselib/multicast.lua Normal file
View File

@@ -0,0 +1,181 @@
---
-- Utility functions for sending MLD requests and parsing reports.
--
-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
local bin = require "bin"
local nmap = require "nmap"
local ipOps = require "ipOps"
local packet = require "packet"
local stdnse = require "stdnse"
local string = require "string"
local table = require "table"
_ENV = stdnse.module("multicast", stdnse.seeall)
---
-- Performs an MLD general query on the selected interface and caches the results such that
-- subsequent calls to this function do not generate additional traffic.
--
-- @param if_nfo A table containing information about the interface to send the request on.
-- Can be one of those returned by nmap.list_interfaces().
-- @param arg_timeout The amount of time to wait for reports.
--
-- @return A list of tables, each table containing three items, namely device, layer 2 reply and layer 3 reply.
--
mld_query = function( if_nfo, arg_timeout )
-- check if the interface name is valid or if nmap can find one
if if_nfo == nil then
return nil
end
-- we need some ID for this interface & address combination to use as the
-- registry key and the object to lock the mutex on
local reg_entry = "mld_reports_" .. if_nfo.device .. "_" .. if_nfo.address
local mutex = nmap.mutex( reg_entry )
mutex('lock')
-- first check if nmap.registry contains reports for this interface from a previous call of this function
if nmap.registry[reg_entry] ~= nil then
mutex('done')
return nmap.registry[reg_entry]
end
if ipOps.ip_to_str(if_nfo.address) == nil -- validate IP address
or not ipOps.ip_in_range(if_nfo.address, "fe80::/10") -- link local address
or if_nfo.link ~= "ethernet" then -- not the loopback interface
mutex('done')
return nil
end
-- create the query packet
local src_mac = if_nfo.mac
local src_ip6 = ipOps.ip_to_str(if_nfo.address)
local dst_mac = packet.mactobin("33:33:00:00:00:01")
local dst_ip6 = ipOps.ip_to_str("ff02::1")
local general_qry = ipOps.ip_to_str("::")
local dnet = nmap.new_dnet()
local pcap = nmap.new_socket()
dnet:ethernet_open(if_nfo.device)
pcap:pcap_open(if_nfo.device, 1500, false, "ip6[40:1] == 58")
local probe = packet.Frame:new()
probe.mac_src = src_mac
probe.mac_dst = dst_mac
probe.ip_bin_src = src_ip6
probe.ip_bin_dst = dst_ip6
probe.ip6_tc = 0
probe.ip6_fl = 0
probe.ip6_hlimit = 1
probe.icmpv6_type = packet.MLD_LISTENER_QUERY
probe.icmpv6_code = 0
-- Add a non-empty payload too.
probe.icmpv6_payload = (
"\x00\x01" .. -- maximum response delay 1 millisecond (if 0, virtualbox TCP/IP stack crashes)
"\x00\x00" .. -- reserved
ipOps.ip_to_str("::") -- empty address - general MLD query
)
probe:build_icmpv6_header()
probe.exheader = bin.pack("CA",
packet.IPPROTO_ICMPV6, -- next header
"\x00" .. -- length not including first 8 octets
"\x05" .. -- type is router alert
"\x02" .. -- length 2 bytes
"\x00\x00" .. -- router alert MLD
"\x01" .. -- padding type PadN
"\x00" -- padding length 0
)
probe.ip6_nhdr = packet.IPPROTO_HOPOPTS
probe:build_ipv6_packet()
probe:build_ether_frame()
-- send the query packet
dnet:ethernet_send(probe.frame_buf)
-- wait for responses to the query packet
pcap:set_timeout(1000)
local pcap_timeout_count = 0
local nse_timeout = arg_timeout or 10
local start_time = nmap:clock()
local addrs = {}
nmap.registry[reg_entry] = {}
repeat
local status, length, layer2, layer3 = pcap:pcap_receive()
local cur_time = nmap:clock()
if status then
local l2reply = packet.Frame:new(layer2)
local l3reply = packet.Packet:new(layer3, length, true)
local target_ip = l3reply.ip_src
if l3reply.ip6_nhdr == packet.MLD_LISTENER_REPORT or l3reply.ip6_nhdr == packet.MLDV2_LISTENER_REPORT then
table.insert(
nmap.registry[reg_entry],
{ if_nfo.device, l2reply, l3reply }
)
end
end
until ( cur_time - start_time >= nse_timeout )
-- clean up
dnet:ethernet_close()
pcap:pcap_close()
mutex('done')
return nmap.registry[reg_entry]
end
---
-- Extracts IP addresses from MLD reports captured by the mld_query function.
--
-- @param reports The output of the mld_query function.
--
-- @return A list of tables, each table containing three items, namely device, mac and a list of addresses.
--
mld_report_addresses = function(reports)
local rep_addresses = {}
for _, report in pairs(reports) do
local device = report[1]
local l2reply = report[2]
local l3reply = report[3]
local target_ip = l3reply.ip_src
if l3reply.ip6_nhdr == packet.MLD_LISTENER_REPORT or l3reply.ip6_nhdr == packet.MLDV2_LISTENER_REPORT then
-- if this is the first reply from the target, make an entry for it
if not rep_addresses[target_ip] then
rep_addresses[target_ip] = {
device = device,
mac = stdnse.format_mac(l2reply.mac_src),
multicast_ips = {}
}
end
-- depending on the MLD version of the report, add appropriate IP addresses
if l3reply.ip6_nhdr == packet.MLD_LISTENER_REPORT then
local multicast_ip = ipOps.str_to_ip( l3reply:raw(0x38, 16) ) -- IP starts at byte 0x38 and is 16 bytes long
table.insert(rep_addresses[target_ip].multicast_ips, multicast_ip)
elseif l3reply.ip6_nhdr == packet.MLDV2_LISTENER_REPORT then
local no_records = l3reply:u16(0x36)
local record_offset = 0
local records_start = 0x38
for i = 1, no_records do
-- for the format description, see RFC3810 (ch. 5.2)
local aux_data_len = l3reply:u8(records_start + record_offset + 1)
local no_sources = l3reply:u16(records_start + record_offset + 2)
local multicast_ip = ipOps.str_to_ip(l3reply:raw(records_start + record_offset + 4, 16))
table.insert(rep_addresses[target_ip].multicast_ips, multicast_ip)
record_offset = record_offset + 4 + 16 + no_sources * 16 + aux_data_len * 4
end
end
end
end
return rep_addresses
end
return _ENV

View File

@@ -1,25 +1,25 @@
local bin = require "bin"
local ipOps = require "ipOps" local ipOps = require "ipOps"
local coroutine = require "coroutine" local coroutine = require "coroutine"
local nmap = require "nmap" local nmap = require "nmap"
local packet = require "packet"
local stdnse = require "stdnse" local stdnse = require "stdnse"
local tab = require "tab" local tab = require "tab"
local table = require "table" local table = require "table"
local target = require "target" local target = require "target"
local multicast = require "multicast"
description = [[ description = [[
Attempts to discover available IPv6 hosts on the LAN by sending an MLD Attempts to discover available IPv6 hosts on the LAN by sending an MLD
(multicast listener discovery) query to the link-local multicast address (multicast listener discovery) query to the link-local multicast address
(ff02::1) and listening for any responses. The query's maximum response delay (ff02::1) and listening for any responses. The query's maximum response delay
set to 0 to provoke hosts to respond immediately rather than waiting for other set to 1 to provoke hosts to respond immediately rather than waiting for other
responses from their multicast group. responses from their multicast group.
]] ]]
--- ---
-- @usage -- @usage
-- nmap -6 --script=targets-ipv6-multicast-mld.nse --script-args 'newtargets,interface=eth0' -sP -- nmap -6 --script=targets-ipv6-multicast-mld.nse --script-args 'newtargets,interface=eth0'
-- --
-- @output
-- Pre-scan script results: -- Pre-scan script results:
-- | targets-ipv6-multicast-mld: -- | targets-ipv6-multicast-mld:
-- | IP: fe80::5a55:abcd:ef01:2345 MAC: 58:55:ab:cd:ef:01 IFACE: en0 -- | IP: fe80::5a55:abcd:ef01:2345 MAC: 58:55:ab:cd:ef:01 IFACE: en0
@@ -29,10 +29,25 @@ responses from their multicast group.
-- --
-- @args targets-ipv6-multicast-mld.timeout timeout to wait for -- @args targets-ipv6-multicast-mld.timeout timeout to wait for
-- responses (default: 10s) -- responses (default: 10s)
-- @args targets-ipv6-multicast-mld.interface Interface to send on (overrides -e) -- @args targets-ipv6-multicast-mld.interface Interface to send on (default:
-- the interface specified with -e or every available Ethernet interface
-- with an IPv6 address.)
-- --
-- @xmloutput
-- <table>
-- <table>
-- <elem key="address">fe80::5a55:abcd:ef01:2345</elem>
-- <elem key="mac">58:55:ab:cd:ef:01</elem>
-- <elem key="iface">en0</elem>
-- </table>
-- <table>
-- <elem key="address">fe80::9284:0123:4567:89ab</elem>
-- <elem key="mac">90:84:01:23:45:67</elem>
-- <elem key="iface">en0</elem>
-- </table>
-- </table>
author = "niteesh" author = "niteesh, alegen"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html" license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"discovery","broadcast"} categories = {"discovery","broadcast"}
@@ -54,19 +69,12 @@ local function get_interfaces()
-- interfaces list (decide which interfaces to broadcast on) -- interfaces list (decide which interfaces to broadcast on)
local interfaces = {} local interfaces = {}
if interface_name then for _, if_table in pairs(nmap.list_interfaces()) do
-- single interface defined if (interface_name == nil or if_table.device == interface_name) -- check for correct interface
local if_table = nmap.get_interface_info(interface_name) and ipOps.ip_to_str(if_table.address) -- validate IP address
if if_table and ipOps.ip_to_str(if_table.address) and if_table.link == "ethernet" then and ipOps.ip_in_range(if_table.address, "fe80::/10") -- link local address
interfaces[#interfaces + 1] = if_table and if_table.link == "ethernet" then -- not the loopback interface
else table.insert(interfaces, if_table)
stdnse.debug1("Interface not supported or not properly configured.")
end
else
for _, if_table in ipairs(nmap.list_interfaces()) do
if ipOps.ip_to_str(if_table.address) and if_table.link == "ethernet" then
table.insert(interfaces, if_table)
end
end end
end end
@@ -76,77 +84,29 @@ end
local function single_interface_broadcast(if_nfo, results) local function single_interface_broadcast(if_nfo, results)
stdnse.debug2("Starting " .. SCRIPT_NAME .. " on " .. if_nfo.device) stdnse.debug2("Starting " .. SCRIPT_NAME .. " on " .. if_nfo.device)
local condvar = nmap.condvar(results) local condvar = nmap.condvar(results)
local src_mac = if_nfo.mac
local src_ip6 = ipOps.ip_to_str(if_nfo.address)
local dst_mac = packet.mactobin("33:33:00:00:00:01")
local dst_ip6 = ipOps.ip_to_str("ff02::1")
local gen_qry = ipOps.ip_to_str("::")
local dnet = nmap.new_dnet() local reports = multicast.mld_query(if_nfo, arg_timeout or 10)
local pcap = nmap.new_socket() for _, r in pairs(reports) do
local l2reply = r[2]
dnet:ethernet_open(if_nfo.device) local l3reply = r[3]
pcap:pcap_open(if_nfo.device, 1500, false, "ip6[40:1] == 58") local target_str = l3reply.ip_src
if not results[target_str] then
local probe = packet.Frame:new() if target.ALLOW_NEW_TARGETS then
probe.mac_src = src_mac target.add(target_str)
probe.mac_dst = dst_mac
probe.ip_bin_src = src_ip6
probe.ip_bin_dst = dst_ip6
probe.ip6_tc = 0
probe.ip6_fl = 0
probe.ip6_hlimit = 1
probe.icmpv6_type = packet.MLD_LISTENER_QUERY
probe.icmpv6_code = 0
-- Add a non-empty payload too.
probe.icmpv6_payload = bin.pack("HA", "00 00 00 00", gen_qry)
probe:build_icmpv6_header()
probe.exheader = bin.pack("CH", packet.IPPROTO_ICMPV6, "00 05 02 00 00 01 00")
probe.ip6_nhdr = packet.IPPROTO_HOPOPTS
probe:build_ipv6_packet()
probe:build_ether_frame()
dnet:ethernet_send(probe.frame_buf)
pcap:set_timeout(1000)
local pcap_timeout_count = 0
local nse_timeout = arg_timeout or 10
local start_time = nmap:clock()
local addrs = {}
repeat
local status, length, layer2, layer3 = pcap:pcap_receive()
local cur_time = nmap:clock()
if ( status ) then
local l2reply = packet.Frame:new(layer2)
local reply = packet.Packet:new(layer3, length, true)
if ( reply.ip6_nhdr == packet.MLD_LISTENER_REPORT or
reply.ip6_nhdr == packet.MLDV2_LISTENER_REPORT ) then
local target_str = reply.ip_src
if not results[target_str] then
if target.ALLOW_NEW_TARGETS then
target.add(target_str)
end
results[target_str] = { address = target_str, mac = stdnse.format_mac(l2reply.mac_src), iface = if_nfo.device }
end
end end
results[target_str] = { address = target_str, mac = stdnse.format_mac(l2reply.mac_src), iface = if_nfo.device }
end end
until ( cur_time - start_time >= nse_timeout ) end
dnet:ethernet_close()
pcap:pcap_close()
condvar("signal") condvar("signal")
end end
local function format_output(results) local function format_output(results)
local output = tab.new() local output = tab.new()
local xmlout = {}
for _, record in pairs(results) do for i, record in ipairs(table.sort(stdnse.keys(results))) do
xmlout[i] = record
tab.addrow(output, "IP: " .. record.address, "MAC: " .. record.mac, "IFACE: " .. record.iface) tab.addrow(output, "IP: " .. record.address, "MAC: " .. record.mac, "IFACE: " .. record.iface)
end end
@@ -156,7 +116,7 @@ local function format_output(results)
table.insert(output, "") table.insert(output, "")
table.insert(output, "Use --script-args=newtargets to add the results as targets") table.insert(output, "Use --script-args=newtargets to add the results as targets")
end end
return stdnse.format_output(true, output) return xmlout, table.concat(output, "\n ")
end end
end end