341 lines
12 KiB
Lua
341 lines
12 KiB
Lua
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
|