216 lines
5.8 KiB
Plaintext
216 lines
5.8 KiB
Plaintext
|
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
|