diff --git a/CHANGELOG b/CHANGELOG index 0912b17f2..c7ae48c07 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ #Nmap Changelog ($Id$); -*-text-*- +o [NSE][GH#711] New script openflow-info gathers preferred and supported + protocol versions from OpenFlow devices [Jay Smith, Mak Kolybabi] + o New UDP payloads: + [GH#1279] TS3INIT1 for UDP 3389 [colcrunch] + [GH#1895] DTLS for UDP 3391 (RD Gateway) [Arnim Rupp] diff --git a/nmap-service-probes b/nmap-service-probes index c03c838ec..794240cac 100644 --- a/nmap-service-probes +++ b/nmap-service-probes @@ -2363,8 +2363,14 @@ match oftp m|^\x10\0\0\x17IODETTE FTP READY \r$| p/ODETTE File Transfer Protocol match oo-defrag m|^\x99\0\0\0\x01\0\0\0\x03\0\0\0\xb9\x08\0\0\x02\0\0\0\x01\0\0\0\0\0\0\0N\x06\0\0\0\0\0\0\x01\0\0\0\0\0\0\0\n\x0b\0\0\0\xe8\xff\x01\0\x95\x8a\x01\0\0\0\0\0\0\0\0\0\x12\0\0\0 o\0\0\x13\0\0\0p\0\0\0\xf5\x01\0\0\x8c\x02\0\0\x1c\x01\0\0\x01\0\0\0\x03\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0gM1\x06\0\0\0\0\x01\0\0\0gM1\x06\0\0\0\0\x98\xadm\t\0\0\0\0\x02\0\0\0\xff\xfa\x9e\x0f\0\0\0\0\0\xff\r\x06\0\0\0\0\x99\0\0\0\x01\0\0\0\x03\0\0\0\xb9\x08\0\0\x02\0\0\0\x01\0\0\0\0\0\0\0N\x06\0\0\0\0\0\0\x01\0\0\0\0\0\0\0\x04\x0b\0\0\0\xe8\xff\x01\0\x95\x8a\x01\0\0\0\0\0\0\0\0\0\x12\0\0\0!o\0\0\x13\0\0\0p\0\0\0\xf5\x01\0\0\x8c\x02\0\0\x1c\x01\0\0\0\0\0\0\x03\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0gM1\x06\0\0\0\0\x01\0\0\0gM1\x06\0\0\0\0\x98\xadm\t\0\0\0\0\x02\0\0\0\xff\xfa\x9e\x0f\0\0\0\0\0\xff\r\x06\0\0\0\0\x99\0\0\0\x01\0\0\0\x03\0\0\0\xb9\x08\0\0\x02\0\0\0\x01\0\0\0\0\0\0\0o\x0e\0\0\0\0\0\0\x01\0\0\0\0\0\0\0\n\x0b\0\0\0\xe8\xff\x01\0\x95\x8a\x01\0\0\0\0\0\0\0\0\0\x12\0\0\0 o\0\0\x13\0\0\0p\0\0\0\xf5\x01\0\0\x8c\x02\0\0\x1c\x01\0\0\x01\0\0\0\x03\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0gM1\x06\0\0\0\0\x01\0\0\0gM1\x06\0\0\0\0\x98\xadm\t\0\0\0\0\x02\0\0\0\xff\xfa\x9e\x0f\0\0\0\0\0\xff\r\x06\0\0\0\x006\x01\0\0\x01\0\0\0\x03\0\0\0\x07\x08\0\0\x02\0\0\0\x07\x052Q\0\0L\^\x03\0\0\0\0\0\xa2\x88\0\0\0\0\0\0\xd9\xe6\x03\0\0\0\0\0\xb9\x02\0\0\0\0\0\0\x0e\x0b\0\0\0\0\0\0\)\xb8\x02\0\0\0\0\0\xed\x07\x95\?\0\0C\xad/\+i\0t\r\0\0\0\0\0\0{{\x16\x05\0\0\0\0\0\0\0\0\xd0\0\0\0((?:[^\0]\0)+)\0\x006\x01\0\0\x01\0\0\0\x03\0\0\0\x07\x08\0\0\x02\0\0\0\x07\x052Q\0\0L\^\x03\0\0\0\0\0\xa2\x88\0\0\0\0\0\0\xd9\xe6\x03\0\0\0\0\0\xb9\x02\0\0\0\0\0\0\x0e\x0b\0\0\0\0\0\0\)\xb8\x02\0\0\0\0\0\xed\x07\x95\?\0\0C\xad/\+i\0t\r\0\0\0\0\0\0{{\x16\x05\0$|s p/O&O Defrag Professional/ v/15/ i/path: $P(1)/ # https://wiki.wireshark.org/OpenFlow -# 4-byte TXID is random in OpenDaylight, sequential in POX -softmatch openflow m|^\x01\0\0\x08....$| i/OpenFlow 1.0/ +# 4-byte TXID is random in OpenDaylight, sequential in POX, and decrementing from 0xFFFFFFFF in floodlight. +# An extension may or may not be sent, account for both cases. +match openflow m|^\x06\0\0(?:\x10....\0\x01\0)?\x08....$|s p/OpenFlow/ v/1.5.x/ +match openflow m|^\x05\0\0(?:\x10....\0\x01\0)?\x08....$|s p/OpenFlow/ v/1.4.x/ +match openflow m|^\x04\0\0(?:\x10....\0\x01\0)?\x08....$|s p/OpenFlow/ v/1.3.x/ +match openflow m|^\x03\0\0(?:\x10....\0\x01\0)?\x08....$|s p/OpenFlow/ v/1.2/ +match openflow m|^\x02\0\0(?:\x10....\0\x01\0)?\x08....$|s p/OpenFlow/ v/1.1/ +match openflow m|^\x01\0\0(?:\x10....\0\x01\0)?\x08....$|s p/OpenFlow/ v/1.0/ match openfpc m|^OFPC READY\n$| p/OpenFPC packet capture/ diff --git a/scripts/openflow-info.nse b/scripts/openflow-info.nse new file mode 100644 index 000000000..8d0232eb6 --- /dev/null +++ b/scripts/openflow-info.nse @@ -0,0 +1,205 @@ +local comm = require "comm" +local shortport = require "shortport" +local stdnse = require "stdnse" +local string = require "string" +local match = require "match" +local table = require "table" + +description = [[ +Queries OpenFlow controllers for information. Newer versions of the OpenFlow +protocol (1.3 and greater) will return a list of all protocol versions supported +by the controller. Versions prior to 1.3 only return their own version number. + +For additional information: +* https://www.opennetworking.org/images/stories/downloads/sdn-resources/onf-specifications/openflow/openflow-switch-v1.5.0.noipr.pdf +]] + +--- +-- @usage nmap -p 6633,6653 --script openflow-info +-- @output +-- PORT STATE SERVICE REASON +-- 6653/tcp open openflow +-- | openflow-info: +-- | OpenFlow Running Version: 1.5.X +-- | OpenFlow Versions Supported: +-- | 1.0 +-- | 1.1 +-- | 1.2 +-- | 1.3.X +-- | 1.4.X +-- |_ 1.5.X + +author = {"Jay Smith", "Mak Kolybabi "} +license = "Same as Nmap--See https://nmap.org/book/man-legal.html" +categories = {"default", "safe"} + +-- OpenFlow versions released: +-- 0x01 = 1.0 +-- 0x02 = 1.1 +-- 0x03 = 1.2 +-- 0x04 = 1.3.X +-- 0x05 = 1.4.X +-- 0x06 = 1.5.X +-- The bits in the version bitmap are indexed by the ofp version number of the +-- protocol. If the bit identified by the number of left bitshift equal +-- to a ofp version number is set, this OpenFlow version is supported. +local openflow_versions = { + [0x02] = "1.0", + [0x04] = "1.1", + [0x08] = "1.2", + [0x10] = "1.3.X", + [0x20] = "1.4.X", + [0x40] = "1.5.X" +} + +local OPENFLOW_HEADER_SIZE = 8 +local OFPT_HELLO = 0 +local OFPHET_VERSIONBITMAP = 1 + +portrule = shortport.version_port_or_service({6633, 6653}, "openflow", "tcp") + +receive_message = function(host, port) + local hello = string.pack( + ">I1 I1 I2 I4", + 0x04, + OFPT_HELLO, + OPENFLOW_HEADER_SIZE, + 0xFFFFFFFF + ) + + -- Handshake Info: + -- Versions 1.3.1 and later say hello with a bitmap of versions supported + -- Earlier versions either say hello without the bitmap. + -- Some implementations are shy and don't make the first move, so we'll say + -- hello first. We'll pretend to be a switch using version 1.0 of the protocol + local socket, response = comm.tryssl(host, port, hello, {bytes = OPENFLOW_HEADER_SIZE}) + if not socket then + stdnse.debug1("Failed to connect to service: %s", response) + return + end + + if #response < OPENFLOW_HEADER_SIZE then + socket:close() + stdnse.debug1("Initial packet received was %d bytes, need >= %d bytes.", #response, OPENFLOW_HEADER_SIZE) + return + end + + -- The first byte is the protocol version number being used. So long as that + -- number is less than the currently-published versions, then we can be + -- confident in our parsing of the packet. + local pos = 1 + local message = {} + local message_version, pos = string.unpack(">I1", response, 1) + if message_version > 0x06 then + socket:close() + stdnse.debug1("Initial packet received had unrecognized version %d.", message_version) + return + end + message.version = message_version + + -- The second byte is the packet type. + local message_type, pos = string.unpack(">I1", response, pos) + message.type = message_type + + -- The fourth and fifth bytes are the length of the entire message, including + -- the header and length itself. + local message_length, pos = string.unpack(">I2", response, pos) + if message_length < OPENFLOW_HEADER_SIZE then + socket:close() + stdnse.debug1("Response declares length as %d bytes, need >= %d bytes.", message_length, OPENFLOW_HEADER_SIZE) + return + end + message.length = message_length + + -- The remainder of the header contains the ID. + local message_id, pos = string.unpack(">I4", response, pos) + message.id = message_id + + -- All remaining data from the response, up until the message length, is the body. + assert(pos == OPENFLOW_HEADER_SIZE + 1) + message.body = response:sub(pos, message_length) + + -- If we have the whole packet, pass it up the call stack. + if message_length <= #response then + socket:close() + return message + end + + -- If message length is larger than the data we already have, receive the + -- remainder of the packet. + local missing_bytes = message_length - #response + local status, body = socket:receive_buf(match.numbytes(missing_bytes), true) + if not status then + socket:close() + stdnse.debug1("Failed to receive missing %d bytes of response: %s", missing_bytes, body) + return + end + message.body = (response .. body):sub(pos, message_length) + + return message +end + +retrieve_version_bitmap = function(message) + -- HELLO message structure: + -- /* OFPT_HELLO. This message includes zero or more hello elements having + -- * variable size. Unknown elements types must be ignored/skipped, to allow + -- * for future extensions. */ + -- struct ofp_hello { + -- struct ofp_header header; + -- /* Hello element list */ + -- struct ofp_hello_elem_header elements[0]; /* List of elements - 0 or more */ + -- }; + -- The HELLO message may contain zero or more hello elements. One of these + -- hello elements may be of the type OFPHET_VERSIONBITMAP. We must search + -- through elements until we find OFPHET_VERSIONBITMAP. + -- Note: As of version 1.5, OFPHET_VERSIONBITMAP is the only standard hello element type. + -- However, we can not assume that this will be the case for long. + local pos = 1 + local body = message.body + while pos + 4 < #body - 1 do + local element_length, element_type + element_type, element_length, pos = string.unpack(">I2 I2", body, pos) + if pos + element_length < #body then + stdnse.debug1("Ran out of data parsing element type %d at position %d.", element_type, pos) + return + end + + if element_type == OFPHET_VERSIONBITMAP then + return string.unpack(">I4", body, pos) + end + + pos = pos + element_length - 4 + end + + return +end + +action = function(host, port) + local output = stdnse.output_table() + + local message = receive_message(host, port) + if not message then + return + end + + output["OpenFlow Version Running"] = openflow_versions[2 ^ message.version] + if message.type ~= OFPT_HELLO then + return output + end + + local version_bitmap = retrieve_version_bitmap(message) + if not version_bitmap then + return output + end + + local supported_versions = {} + for mask, version in pairs(openflow_versions) do + if mask & version_bitmap then + table.insert(supported_versions, version) + end + end + table.sort(supported_versions) + output["OpenFlow Versions Supported"] = supported_versions + + return output +end diff --git a/scripts/script.db b/scripts/script.db index 2c047ee15..2b2965aa7 100644 --- a/scripts/script.db +++ b/scripts/script.db @@ -407,6 +407,7 @@ Entry { filename = "ntp-monlist.nse", categories = { "discovery", "intrusive", } Entry { filename = "omp2-brute.nse", categories = { "brute", "intrusive", } } Entry { filename = "omp2-enum-targets.nse", categories = { "discovery", "safe", } } Entry { filename = "omron-info.nse", categories = { "discovery", "version", } } +Entry { filename = "openflow-info.nse", categories = { "default", "safe", } } Entry { filename = "openlookup-info.nse", categories = { "default", "discovery", "safe", "version", } } Entry { filename = "openvas-otp-brute.nse", categories = { "brute", "intrusive", } } Entry { filename = "openwebnet-discovery.nse", categories = { "discovery", "safe", } }