diff --git a/CHANGELOG b/CHANGELOG index dd2e2dc9d..0973ebb8b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ # Nmap Changelog ($Id$); -*-text-*- +o [NSE] Added broadcast-ripng-discover that discovers IPv6 RIPng routers and + displays their routing information. [Patrik] + o [NSE] Made gathered CPE codes available to NSE. [Henri] o [NSE] Fixed a memory leak in PortList::setServiceProbeResults() noticed and diff --git a/scripts/broadcast-ripng-discover.nse b/scripts/broadcast-ripng-discover.nse new file mode 100644 index 000000000..87e72d19a --- /dev/null +++ b/scripts/broadcast-ripng-discover.nse @@ -0,0 +1,211 @@ +description = [[ +Discovers hosts and routing information from devices running RIPng on the +LAN. It does so by sending a RIPng Request command and collects the responses +from all devices responding to the request. +]] + +--- +-- @usage +-- nmap --script broadcast-ripng-discover +-- +-- @output +-- | broadcast-ripng-discover: +-- | fe80::a00:27ff:fe9a:880c +-- | route metric next hop +-- | fe80:470:0:0:0:0:0:0/64 1 +-- | fe80:471:0:0:0:0:0:0/64 1 +-- |_ fe80:472:0:0:0:0:0:0/64 1 +-- +-- @args broadcast-ripng-discover.timeout sets the connection timeout in ms +-- (default: 5000ms) + +author = "Patrik Karlsson" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"broadcast", "safe"} + +require 'ipOps' +require 'tab' + +prerule = function() return ( nmap.address_family() == "inet6" ) end + +RIPng = { + + -- Supported RIPng commands + Command = { + Request = 1, + Response = 2, + }, + + -- Route table entry + RTE = { + + -- Creates a new Route Table Entry + -- @param prefix string containing the ipv6 route prefix + -- @param tag number containing the route tag + -- @param prefix_len number containing the length in bits of the + -- signifcant part of the prefix + -- @param metric number containing the current metric for the + -- destination + new = function(self, prefix, tag, prefix_len, metric) + local o = { + prefix = prefix, + tag = tag, + prefix_len = prefix_len, + metric = metric + } + setmetatable(o, self) + self.__index = self + return o + end, + + -- Parses a byte string and creates an instance of RTE + -- @param data string of bytes + -- @return rte instance of RTE + parse = function(data) + local rte = RIPng.RTE:new() + local pos, ip + + pos, ip, rte.tag, rte.prefix_len, rte.metric = bin.unpack(">A16SCC", data) + ip = select(2, bin.unpack("B" .. #ip, ip)) + rte.prefix = ipOps.bin_to_ip(ip) + return rte + end, + + -- Converts a RTE instance to string + -- @return string of bytes to send to the server + __tostring = function(self) + local ipstr = ipOps.ip_to_str(self.prefix) + assert(16 == #ipstr, "Invalid IPv6 address encountered") + return bin.pack(">ASCC", ipstr, self.tag, self.prefix_len, self.metric) + end, + + + }, + + -- The Request class contains functions to build a RIPv2 Request + Request = { + + -- Creates a new Request instance + -- + -- @param command number containing the RIPv2 Command to use + -- @return o instance of request + new = function(self, entries) + local o = { + command = 1, + version = 1, + entries = entries, + } + setmetatable(o, self) + self.__index = self + return o + end, + + -- Converts the whole request to a string + __tostring = function(self) + local RESERVED = 0 + local str = bin.pack(">CCS", self.command, self.version, RESERVED) + for _, rte in ipairs(self.entries) do + str = str .. tostring(rte) + end + return str + end, + + }, + + -- A RIPng Response + Response = { + + -- Creates a new Response instance + -- @return o new instance of Response + new = function(self) + local o = { } + setmetatable(o, self) + self.__index = self + return o + end, + + -- Creates a new Response instance based on a string of bytes + -- @return resp new instance of Response + parse = function(data) + local resp = RIPng.Response:new() + local pos, _ + + pos, resp.command, resp.version, _ = bin.unpack(">CCS", data) + resp.entries = {} + while( pos < #data ) do + local e = RIPng.RTE.parse(data:sub(pos)) + table.insert(resp.entries, e) + pos = pos + 20 + end + + return resp + end, + } +} + +local function fail(err) return ("\n ERROR: %s"):format(err or "") end + +-- Parses a RIPng response +-- @return ret string containing the routing table +local function parse_response(resp) + local next_hop + local result = tab.new(3) + tab.addrow(result, "route", "metric", "next hop") + for _, rte in pairs(resp.entries or {}) do + -- next hop information is specified in a separate RTE according to + -- RFC 2080 section 2.1.1 + if ( 0xFF == rte.metric ) then + next_hop = rte.prefix + else + tab.addrow(result, ("%s/%d"):format(rte.prefix, rte.prefix_len), rte.metric, next_hop or "") + end + end + return tab.dump(result) +end + +action = function() + + local req = RIPng.Request:new( { RIPng.RTE:new("0::", 0, 0, 16) } ) + local host, port = "FF02::9", { number = 521, protocol = "udp" } + local iface = nmap.get_interface() + local timeout = stdnse.get_script_args(SCRIPT_NAME..".timeout") or 5000 + + local sock = nmap.new_socket("udp") + sock:bind(nil, 521) + sock:set_timeout(timeout) + + local status = sock:sendto(host, port, tostring(req)) + + -- do we need to add the interface name to the address? + if ( not(status) ) then + if ( not(iface) ) then + return fail("Couldn't determine what interface to use, try supplying it with -e") + end + status = sock:sendto(host .. "%" .. iface, port, tostring(req)) + end + + if ( not(status) ) then + return fail("Failed to send request to server") + end + + local responses = {} + while(true) do + local status, data = sock:receive() + if ( not(status) ) then + break + else + local status, _, _, rhost = sock:get_info() + if ( not(status) ) then + rhost = "unknown" + end + responses[rhost] = RIPng.Response.parse(data) + end + end + + local result = {} + for ip, resp in pairs(responses) do + print(ip, resp) + table.insert(result, { name = ip, parse_response(resp) } ) + end + return stdnse.format_output(true, result) +end \ No newline at end of file diff --git a/scripts/script.db b/scripts/script.db index 1a6aac706..66b4d954b 100644 --- a/scripts/script.db +++ b/scripts/script.db @@ -29,6 +29,7 @@ Entry { filename = "broadcast-pc-duo.nse", categories = { "broadcast", "safe", } Entry { filename = "broadcast-ping.nse", categories = { "broadcast", "discovery", "safe", } } Entry { filename = "broadcast-pppoe-discover.nse", categories = { "broadcast", "safe", } } Entry { filename = "broadcast-rip-discover.nse", categories = { "broadcast", "safe", } } +Entry { filename = "broadcast-ripng-discover.nse", categories = { "broadcast", "safe", } } Entry { filename = "broadcast-sybase-asa-discover.nse", categories = { "broadcast", "safe", } } Entry { filename = "broadcast-upnp-info.nse", categories = { "broadcast", "safe", } } Entry { filename = "broadcast-wake-on-lan.nse", categories = { "broadcast", "safe", } }