Created
October 24, 2016 20:40
-
-
Save emyl/8b2e8d69d6b0c6dc91fc16340196883c to your computer and use it in GitHub Desktop.
NSE script for discovering IPv4 networks using Open Shortest Path First version 2(OSPFv2) protocol.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| local bin = require "bin" | |
| local ipOps = require "ipOps" | |
| local nmap = require "nmap" | |
| local ospf = require "ospf" | |
| local packet = require "packet" | |
| local stdnse = require "stdnse" | |
| local target = require "target" | |
| description = [[ | |
| Discover IPv4 networks using Open Shortest Path First version 2(OSPFv2) protocol. | |
| The script works by listening for OSPF Hello packets from the 224.0.0.5 | |
| multicast address. The script then replies and attempts to create a neighbor | |
| relationship, in order to discover network database. | |
| If no interface was provided as a script argument or through the -e option, | |
| the script will fail unless a single interface is present on the system. | |
| ]] | |
| --- | |
| -- @usage | |
| -- nmap --script=ospf2-networks <targets> | |
| -- nmap --script=ospf2-networks <targets> -e wlan0 | |
| -- | |
| -- @args ospf2-networks.timeout Time in seconds that the script waits for | |
| -- hello from other routers. Defaults to 10 seconds, matching OSPFv2 default | |
| -- value for hello interval. | |
| -- | |
| -- @args ospf2-networks.interface Interface to send on (overrides -e). Mandatory | |
| -- if not using -e and multiple interfaces are present. | |
| -- | |
| -- @output | |
| -- Pre-scan script results: | |
| -- | ospf2-networks: | |
| -- | 192.168.2.2 | |
| -- | Area ID: 0.0.0.0 | |
| -- | External Route | |
| -- | Protocol: OSPF | |
| -- | Originating Router ID: 192.168.31.1 | |
| -- | Destination: 192.168.24.0/24 | |
| -- | Next hop: 192.168.31.1 | |
| -- |_ Use the newtargets script-arg to add the results as targets | |
| -- | |
| author = "Emiliano Ticci" | |
| license = "Same as Nmap--See http://nmap.org/book/man-legal.html" | |
| categories = {"broadcast", "discovery", "safe"} | |
| prerule = function() | |
| if nmap.address_family() ~= "inet" then | |
| stdnse.print_verbose("is IPv4 only.") | |
| return false | |
| end | |
| if not nmap.is_privileged() then | |
| stdnse.print_verbose("not running for lack of privileges.") | |
| return false | |
| end | |
| return true | |
| end | |
| -- Script constants | |
| OSPF_ALL_ROUTERS = "224.0.0.5" | |
| OSPF_MSG_HELLO = 0x01 | |
| OSPF_MSG_DBDESC = 0x02 | |
| -- Convenience functions | |
| local function fail(err) return stdnse.format_output(false, err) end | |
| -- Print OSPFv2 Database Description packet details to debug output. | |
| -- @param hello OSPFv2 Database Description packet | |
| local ospfDumpDBDesc = function(db_desc) | |
| stdnse.print_debug(2, "| MTU: %s", db_desc.mtu) | |
| stdnse.print_debug(2, "| Options: %s", db_desc.options) | |
| stdnse.print_debug(2, "| Init: %s", db_desc.init) | |
| stdnse.print_debug(2, "| More: %s", db_desc.more) | |
| stdnse.print_debug(2, "| Master: %s", db_desc.master) | |
| stdnse.print_debug(2, "| Sequence: %s", db_desc.sequence) | |
| end | |
| -- Print OSPFv2 Hello packet details to debug output. | |
| -- @param hello OSPFv2 Hello packet | |
| local ospfDumpHello = function(hello) | |
| stdnse.print_debug(2, "| Router ID: %s", hello.header.router_id) | |
| stdnse.print_debug(2, "| Area ID: %s", hello.header.area_id) | |
| stdnse.print_debug(2, "| Netmask: %s", hello.netmask) | |
| stdnse.print_debug(2, "| Hello interval: %s", hello.interval) | |
| stdnse.print_debug(2, "| Options: %s", hello.options) | |
| stdnse.print_debug(2, "| Priority: %s", hello.prio) | |
| stdnse.print_debug(2, "| Dead interval: %s", hello.router_dead_interval) | |
| stdnse.print_debug(2, "| Designated Router: %s", hello.DR) | |
| stdnse.print_debug(2, "| Backup Router: %s", hello.BDR) | |
| stdnse.print_debug(2, "| Neighbors: %s", table.concat(hello.neighbors, ",")) | |
| end | |
| -- Send OSPFv2 packet to specified destination. | |
| -- @param interface Source interface to use | |
| -- @param ip_dst Destination IP address | |
| -- @param mac_dst Destination MAC address | |
| -- @param ospf_packet Raw OSPF packet | |
| local ospfSend = function(interface, ip_dst, mac_dst, ospf_packet) | |
| local dnet = nmap.new_dnet() | |
| local probe = packet.Frame:new() | |
| probe.mac_src = interface.mac | |
| probe.mac_dst = mac_dst | |
| probe.ip_bin_src = ipOps.ip_to_str(interface.address) | |
| probe.ip_bin_dst = ipOps.ip_to_str(ip_dst) | |
| probe.l3_packet = ospf_packet | |
| probe.ip_dsf = 0xc0 | |
| probe.ip_p = 89 | |
| probe.ip_ttl = 1 | |
| probe:build_ip_packet() | |
| probe:build_ether_frame() | |
| dnet:ethernet_open(interface.device) | |
| dnet:ethernet_send(probe.frame_buf) | |
| dnet:ethernet_close() | |
| end | |
| -- Reply to given OSPFv2 Database Description packet. | |
| -- @param interface Source interface | |
| -- @param mac_dst Destination MAC address | |
| -- @param db_desc_in OSPFv2 Database Description packet to reply for | |
| local ospfReplyDBDesc = function(interface, mac_dst, db_desc_in) | |
| local db_desc_out = ospf.OSPF.DBDescription:new() | |
| db_desc_out.header:setRouterId(interface.address) | |
| db_desc_out.header:setAreaID(db_desc_in.header.area_id) | |
| db_desc_out.init = false | |
| db_desc_out.more = db_desc_in.more | |
| db_desc_out.master = false | |
| db_desc_out.sequence = db_desc_in.sequence | |
| stdnse.print_debug(2, "Crafted OSPFv2 Database Description packet with the following parameters:") | |
| ospfDumpDBDesc(db_desc_out) | |
| ospfSend(interface, db_desc_in.header.router_id, mac_dst, tostring(db_desc_out)) | |
| end | |
| -- Reply to given OSPFv2 Hello packet sending another Hello to | |
| -- "All OSPF Routers" multicast address (224.0.0.5). | |
| -- @param interface Source interface | |
| -- @param hello_in OSPFv2 Hello packet to reply for | |
| local ospfReplyHello = function(interface, hello_in) | |
| local hello_out = ospf.OSPF.Hello:new() | |
| hello_out.header:setRouterId(interface.address) | |
| hello_out.header:setAreaID(hello_in.header.area_id) | |
| hello_out:setDesignatedRouter(hello_in.header.router_id) | |
| hello_out:setNetmask(hello_in.netmask) | |
| hello_out:addNeighbor(hello_in.header.router_id) | |
| stdnse.print_debug(2, "Crafted OSPFv2 Hello packet with the following parameters:") | |
| ospfDumpHello(hello_out) | |
| ospfSend(interface, OSPF_ALL_ROUTERS, "\x01\x00\x5e\x00\x00\x05", tostring(hello_out)) | |
| end | |
| -- Listen for OSPF packets on a specified interface. | |
| -- @param interface Interface to use | |
| -- @param timeout Amount of time to listen in seconds | |
| local ospfListen = function(interface, timeout) | |
| local status, l2_data, l3_data, ospf_raw, _ | |
| local start = nmap.clock_ms() | |
| stdnse.print_debug("Start listening on interface %s...", interface.shortname) | |
| local listener = nmap.new_socket() | |
| listener:set_timeout(1000) | |
| listener:pcap_open(interface.device, 1024, true, "ip proto 89 and not (ip src host " .. interface.address .. ")") | |
| while (nmap.clock_ms() - start) < (timeout * 1000) do | |
| status, _, l2_data, l3_data = listener:pcap_receive() | |
| if status then | |
| stdnse.print_debug(2, "Packet received on interface %s.", interface.shortname) | |
| local p = packet.Packet:new(l3_data, #l3_data) | |
| local ospf_raw = string.sub(l3_data, p.ip_hl*4 + 1) | |
| if ospf_raw:byte(1) == 0x02 and ospf_raw:byte(2) == OSPF_MSG_HELLO then | |
| stdnse.print_debug(2, "OSPFv2 Hello packet detected.") | |
| local ospf_hello = ospf.OSPF.Hello.parse(ospf_raw) | |
| stdnse.print_debug(2, "Captured OSPFv2 Hello packet with the following parameters:") | |
| ospfDumpHello(ospf_hello) | |
| ospfReplyHello(interface, ospf_hello) | |
| elseif ospf_raw:byte(1) == 0x02 and ospf_raw:byte(2) == OSPF_MSG_DBDESC then | |
| stdnse.print_debug(2, "OSPFv2 Database Description packet detected.") | |
| local ospf_db_desc = ospf.OSPF.DBDescription.parse(ospf_raw) | |
| stdnse.print_debug(2, "Captured OSPFv2 Database Description packet with the following parameters:") | |
| ospfDumpDBDesc(ospf_db_desc) | |
| ospfReplyDBDesc(interface, string.sub(l2_data, 7, 12), ospf_db_desc) | |
| end | |
| end | |
| end | |
| listener:pcap_close() | |
| end | |
| action = function() | |
| -- Get script arguments | |
| local timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout")) or 10 | |
| local interface = stdnse.get_script_args(SCRIPT_NAME .. ".interface") | |
| stdnse.print_debug("Value for timeout argument: %s.", timeout) | |
| -- Determine interface to use | |
| interface = interface or nmap.get_interface() | |
| if interface then | |
| interface = nmap.get_interface_info(interface) | |
| if not interface then | |
| return fail(("Failed to retrieve %s interface information."):format(interface)) | |
| end | |
| stdnse.print_debug("Will use %s interface.", interface.shortname) | |
| else | |
| local interface_list = nmap.list_interfaces() | |
| local interface_good = {} | |
| for _, os_interface in ipairs(interface_list) do | |
| if os_interface.address and os_interface.link == "ethernet" and | |
| os_interface.address:match("%d+%.%d+%.%d+%.%d+") then | |
| stdnse.print_debug(2, "Found usable interface: %s.", os_interface.shortname) | |
| table.insert(interface_good, os_interface) | |
| end | |
| end | |
| if #interface_good == 1 then | |
| interface = interface_good[1] | |
| stdnse.print_debug("Will use %s interface.", interface.shortname) | |
| elseif #interface_good == 0 then | |
| return fail("Source interface not found.") | |
| else | |
| return fail("Ambiguous source interface, please specify it with -e or interface parameter.") | |
| end | |
| end | |
| ospfListen(interface, timeout) | |
| print("End of ospf2-networks script.") | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment