diff options
Diffstat (limited to 'scripts/broadcast-ripng-discover.nse')
-rw-r--r-- | scripts/broadcast-ripng-discover.nse | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/scripts/broadcast-ripng-discover.nse b/scripts/broadcast-ripng-discover.nse new file mode 100644 index 0000000..82f90dd --- /dev/null +++ b/scripts/broadcast-ripng-discover.nse @@ -0,0 +1,215 @@ +local ipOps = require "ipOps" +local nmap = require "nmap" +local stdnse = require "stdnse" +local string = require "string" +local tab = require "tab" +local table = require "table" + +description = [[ +Discovers hosts and routing information from devices running RIPng on the +LAN by sending a broadcast RIPng Request command and collecting any responses. +]] + +--- +-- @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 +-- (default: 5s) + +author = "Patrik Karlsson" +license = "Same as Nmap--See https://nmap.org/book/man-legal.html" +categories = {"broadcast", "safe"} + + +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 + -- significant 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 + + ip, rte.tag, rte.prefix_len, rte.metric, pos = string.unpack(">c16 I2 BB", data) + rte.prefix = ipOps.str_to_ip(ip, 'inet6') + 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 ipstr .. string.pack(">I2 BB", 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 = {string.pack(">BB I2", self.command, self.version, RESERVED)} + for _, rte in ipairs(self.entries) do + str[#str+1] = tostring(rte) + end + return table.concat(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, _ + + resp.command, resp.version, _, pos = string.unpack(">BB I2", 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 stdnse.format_output(false, err) 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.parse_timespec(stdnse.get_script_args(SCRIPT_NAME..".timeout")) + timeout = (timeout or 5) * 1000 + + 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 + stdnse.debug1(ip, resp) + table.insert(result, { name = ip, parse_response(resp) } ) + end + return stdnse.format_output(true, result) +end |