diff options
Diffstat (limited to 'scripts/broadcast-eigrp-discovery.nse')
-rw-r--r-- | scripts/broadcast-eigrp-discovery.nse | 340 |
1 files changed, 340 insertions, 0 deletions
diff --git a/scripts/broadcast-eigrp-discovery.nse b/scripts/broadcast-eigrp-discovery.nse new file mode 100644 index 0000000..5b47ab4 --- /dev/null +++ b/scripts/broadcast-eigrp-discovery.nse @@ -0,0 +1,340 @@ +local eigrp = require "eigrp" +local nmap = require "nmap" +local stdnse = require "stdnse" +local table = require "table" +local packet = require "packet" +local ipOps = require "ipOps" +local target = require "target" +local coroutine = require "coroutine" +local string = require "string" + +description = [[ +Performs network discovery and routing information gathering through +Cisco's Enhanced Interior Gateway Routing Protocol (EIGRP). + +The script works by sending an EIGRP Hello packet with the specified Autonomous +System value to the 224.0.0.10 multicast address and listening for EIGRP Update +packets. The script then parses the update responses for routing information. + +If no A.S value was provided by the user, the script will listen for multicast +Hello packets to grab an A.S value. If no interface was provided as a script +argument or through the -e option, the script will send packets and listen +through all valid ethernet interfaces simultaneously. + +]] + +--- +-- @usage +-- nmap --script=broadcast-eigrp-discovery <targets> +-- nmap --script=broadcast-eigrp-discovery <targets> -e wlan0 +-- +-- @args broadcast-eigrp-discovery.as Autonomous System value to announce on. +-- If not set, the script will listen for announcements on 224.0.0.10 to grab +-- an A.S value. +-- +-- @args broadcast-eigrp-discovery.timeout Max amount of time to listen for A.S +-- announcements and updates. Defaults to <code>10</code> seconds. +-- +-- @args broadcast-eigrp-discovery.kparams the K metrics. +-- Defaults to <code>101000</code>. +-- @args broadcast-eigrp-discovery.interface Interface to send on (overrides -e) +-- +--@output +-- Pre-scan script results: +-- | broadcast-eigrp-discovery: +-- | 192.168.2.2 +-- | Interface: eth0 +-- | A.S: 1 +-- | Virtual Router ID: 0 +-- | Internal Route +-- | Destination: 192.168.21.0/24 +-- | Next hop: 0.0.0.0 +-- | Internal Route +-- | Destination: 192.168.31.0/24 +-- | Next hop: 0.0.0.0 +-- | External Route +-- | Protocol: Static +-- | Originating A.S: 0 +-- | Originating Router ID: 192.168.31.1 +-- | Destination: 192.168.60.0/24 +-- | Next hop: 0.0.0.0 +-- | External Route +-- | Protocol: OSPF +-- | Originating A.S: 1 +-- | Originating Router ID: 192.168.31.1 +-- | Destination: 192.168.24.0/24 +-- | Next hop: 0.0.0.0 +-- |_ Use the newtargets script-arg to add the results as targets +-- + +author = "Hani Benhabiles" + +license = "Same as Nmap--See https://nmap.org/book/man-legal.html" + +categories = {"discovery", "broadcast", "safe"} + +prerule = function() + if nmap.address_family() ~= 'inet' then + stdnse.verbose1("is IPv4 only.") + return false + end + if not nmap.is_privileged() then + stdnse.verbose1("not running for lack of privileges.") + return false + end + return true +end + + +-- Sends EIGRP raw packet to EIGRP multicast address. +--@param interface Network interface to use. +--@param eigrp_raw Raw eigrp packet. +local eigrpSend = function(interface, eigrp_raw) + local srcip = interface.address + local dstip = "224.0.0.10" + + local ip_raw = stdnse.fromhex( "45c00040ed780000015818bc0a00c8750a00c86b") .. eigrp_raw + local eigrp_packet = packet.Packet:new(ip_raw, ip_raw:len()) + eigrp_packet:ip_set_bin_src(ipOps.ip_to_str(srcip)) + eigrp_packet:ip_set_bin_dst(ipOps.ip_to_str(dstip)) + eigrp_packet:ip_set_len(#eigrp_packet.buf) + eigrp_packet:ip_count_checksum() + + local sock = nmap.new_dnet() + sock:ethernet_open(interface.device) + -- Ethernet IPv4 multicast, our ethernet address and packet type IP + local eth_hdr = stdnse.fromhex("01 00 5e 00 00 0a") .. interface.mac .. stdnse.fromhex("08 00") + sock:ethernet_send(eth_hdr .. eigrp_packet.buf) + sock:ethernet_close() +end + + +-- Listens for EIGRP updates +--@param interface Network interface to listen on. +--@param timeout Ammount of time to listen for. +--@param responses Table to put valid responses into. +local eigrpListener = function(interface, timeout, responses) + local condvar = nmap.condvar(responses) + local routers = {} + local status, l3data, response, p, eigrp_raw, _ + local start = nmap.clock_ms() + -- Filter for EIGRP packets that are sent either to us or to multicast + local filter = "ip proto 88 and (ip dst host " .. interface.address .. " or 224.0.0.10)" + local listener = nmap.new_socket() + listener:set_timeout(500) + listener:pcap_open(interface.device, 1024, true, filter) + + -- For each EIGRP packet captured until timeout + while (nmap.clock_ms() - start) < timeout do + response = {} + response.tlvs = {} + status, _, _, l3data = listener:pcap_receive() + if status then + p = packet.Packet:new(l3data, #l3data) + eigrp_raw = string.sub(l3data, p.ip_hl*4 + 1) + -- Check if it is an EIGRPv2 Update + if eigrp_raw:byte(1) == 0x02 and eigrp_raw:byte(2) == 0x01 then + -- Skip if did get the info from this router before + if not routers[p.ip_src] then + -- Parse header + response = eigrp.EIGRP.parse(eigrp_raw) + response.src = p.ip_src + response.interface = interface.shortname + end + if response then + -- See, if it has routing information + for _,tlv in pairs(response.tlvs) do + if eigrp.EIGRP.isRoutingTLV(tlv.type) then + routers[p.ip_src] = true + table.insert(responses, response) + break + end + end + end + end + end + end + condvar("signal") + return +end + +-- Listens for EIGRP announcements to grab a valid Autonomous System value. +--@param interface Network interface to listen on. +--@param timeout Max amount of time to listen for. +--@param astab Table to put result into. +local asListener = function(interface, timeout, astab) + local condvar = nmap.condvar(astab) + local status, l3data, p, eigrp_raw, eigrp_hello, _ + local start = nmap.clock_ms() + local filter = "ip proto 88 and ip dst host 224.0.0.10" + local listener = nmap.new_socket() + listener:set_timeout(500) + listener:pcap_open(interface.device, 1024, true, filter) + while (nmap.clock_ms() - start) < timeout do + -- Check if another listener already found an A.S value. + if #astab > 0 then break end + + status, _, _, l3data = listener:pcap_receive() + if status then + p = packet.Packet:new(l3data, #l3data) + eigrp_raw = string.sub(l3data, p.ip_hl*4 + 1) + -- Listen for EIGRPv2 Hello packets + if eigrp_raw:byte(1) == 0x02 and eigrp_raw:byte(2) == 0x05 then + eigrp_hello = eigrp.EIGRP.parse(eigrp_raw) + if eigrp_hello and eigrp_hello.as then + table.insert(astab, eigrp_hello.as) + break + end + end + end + end + condvar("signal") +end + +local function fail (err) return stdnse.format_output(false, err) end + +action = function() + -- Get script arguments + local as = stdnse.get_script_args(SCRIPT_NAME .. ".as") + local kparams = stdnse.get_script_args(SCRIPT_NAME .. ".kparams") or "101000" + local timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout")) + local interface = stdnse.get_script_args(SCRIPT_NAME .. ".interface") + local output, responses, interfaces, lthreads = {}, {}, {}, {} + local result, response, route, eigrp_hello, k + local timeout = (timeout or 10) * 1000 + + -- K params should be of length 6 + -- Cisco routers ignore eigrp packets that don't have matching K parameters + if #kparams < 6 or #kparams > 6 then + return fail("kparams should be of size 6.") + else + k = {} + k[1] = string.sub(kparams, 1,1) + k[2] = string.sub(kparams, 2,2) + k[3] = string.sub(kparams, 3,3) + k[4] = string.sub(kparams, 4,4) + k[5] = string.sub(kparams, 5,5) + k[6] = string.sub(kparams, 6) + end + + interface = interface or nmap.get_interface() + if interface then + -- If an interface was provided, get its information + interface = nmap.get_interface_info(interface) + if not interface then + return fail(("Failed to retrieve %s interface information."):format(interface)) + end + interfaces = {interface} + stdnse.debug1("Will use %s interface.", interface.shortname) + else + local ifacelist = nmap.list_interfaces() + for _, iface in ipairs(ifacelist) do + -- Match all ethernet interfaces + if iface.address and iface.link=="ethernet" and + iface.address:match("%d+%.%d+%.%d+%.%d+") then + + stdnse.debug1("Will use %s interface.", iface.shortname) + table.insert(interfaces, iface) + end + end + end + + -- If user didn't provide an Autonomous System value, we listen fro multicast + -- HELLO router announcements to get one. + if not as then + -- We use a table for condvar + local astab = {} + stdnse.debug1("No A.S value provided, will sniff for one.") + -- We should iterate over interfaces + for _, interface in pairs(interfaces) do + local co = stdnse.new_thread(asListener, interface, timeout, astab) + lthreads[co] = true + end + local condvar = nmap.condvar(astab) + -- Wait for the listening threads to finish + repeat + for thread in pairs(lthreads) do + if coroutine.status(thread) == "dead" then lthreads[thread] = nil end + end + if ( next(lthreads) ) then + condvar("wait") + end + until next(lthreads) == nil; + + if #astab > 0 then + stdnse.debug1("Will use %s A.S value.", astab[1]) + as = astab[1] + else + return fail("Couldn't get an A.S value.") + end + end + + -- Craft Hello packet + eigrp_hello = eigrp.EIGRP:new(eigrp.OPCODE.HELLO, as) + -- K params + eigrp_hello:addTLV({ type = eigrp.TLV.PARAM, k = k, htime = 15}) + -- Software version + eigrp_hello:addTLV({ type = eigrp.TLV.SWVER, majv = 12, minv = 4, majtlv = 1, mintlv = 2}) + + -- On each interface, launch the listening thread and send the Hello packet. + lthreads = {} + for _, interface in pairs(interfaces) do + local co = stdnse.new_thread(eigrpListener, interface, timeout, responses) + -- We insert a small delay before sending the Hello so the listening + -- thread doesn't miss updates. + stdnse.sleep(0.5) + lthreads[co] = true + eigrpSend(interface, tostring(eigrp_hello)) + end + + local condvar = nmap.condvar(responses) + -- Wait for the listening threads to finish + repeat + condvar("wait") + for thread in pairs(lthreads) do + if coroutine.status(thread) == "dead" then lthreads[thread] = nil end + end + until next(lthreads) == nil; + + -- Output the useful info from the responses + if #responses > 0 then + for _, response in pairs(responses) do + result = {} + result.name = response.src + if target.ALLOW_NEW_TARGETS then target.add(response.src) end + table.insert(result, "Interface: " .. response.interface) + table.insert(result, ("A.S: %d"):format(response.as)) + table.insert(result, ("Virtual Router ID: %d"):format(response.routerid)) + -- Output routes information TLVs + for _, tlv in pairs(response.tlvs) do + route = {} + -- We are only interested in Internal or external routes + if tlv.type == eigrp.TLV.EXT then + route.name = "External route" + for name, value in pairs(eigrp.EXT_PROTO) do + if value == tlv.eproto then + table.insert(route, ("Protocol: %s"):format(name)) + break + end + end + table.insert(route, ("Originating A.S: %s"):format(tlv.oas)) + table.insert(route, ("Originating Router ID: %s"):format(tlv.orouterid)) + if target.ALLOW_NEW_TARGETS then target.add(tlv.orouterid) end + table.insert(route, ("Destination: %s/%d"):format(tlv.dst, tlv.mask)) + table.insert(route, ("Next hop: %s"):format(tlv.nexth)) + table.insert(result, route) + elseif tlv.type == eigrp.TLV.INT then + route.name = "Internal route" + table.insert(route, ("Destination: %s/%d"):format(tlv.dst, tlv.mask)) + table.insert(route, ("Next hop: %s"):format(tlv.nexth)) + table.insert(result, route) + end + end + table.insert(output, result) + end + if #output>0 and not target.ALLOW_NEW_TARGETS then + table.insert(output,"Use the newtargets script-arg to add the results as targets") + end + return stdnse.format_output(true, output) + end +end |