From 3d7250ecc47813dd8f32a407d89f8e766643a1d0 Mon Sep 17 00:00:00 2001 From: patrik Date: Tue, 29 May 2012 18:02:19 +0000 Subject: [PATCH] o [NSE] Added the Internet Storage Name Service (iSNS) library and the isns-info script that lists information about portals and iSCSI devices. [Patrik Karlsson] --- CHANGELOG | 4 + nselib/isns.lua | 563 ++++++++++++++++++++++++++++++++++++++++++ scripts/isns-info.nse | 70 ++++++ scripts/script.db | 4 +- 4 files changed, 640 insertions(+), 1 deletion(-) create mode 100644 nselib/isns.lua create mode 100644 scripts/isns-info.nse diff --git a/CHANGELOG b/CHANGELOG index e2004304b..29f63fee6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,9 @@ # Nmap Changelog ($Id$); -*-text-*- +o [NSE] Added the Internet Storage Name Service (iSNS) library and the + isns-info script that lists information about portals and iSCSI devices. + [Patrik Karlsson] + o [NSE] Added rmi-vuln-classloader which scans for machines vulnerable to remote class loading. [Aleksandar Nikolic] diff --git a/nselib/isns.lua b/nselib/isns.lua new file mode 100644 index 000000000..fe1f0cb3a --- /dev/null +++ b/nselib/isns.lua @@ -0,0 +1,563 @@ +--- +-- A minimal Internet Storage Name Service (iSNS) implementation +-- +-- @author "Patrik Karlsson " +-- + +local bin = require('bin') +local bit = require('bit') +local ipops = require('ipOps') +local match = require('match') +local stdnse = require('stdnse') +local tab = require('tab') +_ENV = stdnse.module("isns", stdnse.seeall); + +iSCSI = { + + NodeType = { + TARGET = 1, + INITIATOR = 2, + CONTROL = 4, + } + +} + + +Header = { + + VERSION = 1, + + -- + -- Creates a header instance + -- + -- @param func_id number containing the function ID of the message + -- @param pdu_len number containing the length of the PDU + -- @param flags number containing the message flags + -- @param trans_id number containing the transaction id + -- @param seq_id number containing the sequence id + -- @return o new class instance + new = function(self, func_id, pdu_len, flags, trans_id, seq_id) + local o = { + ver = Header.VERSION, + func_id = func_id, + flags = flags, + trans_id = trans_id, + seq_id = seq_id, + pdu_len = pdu_len, + } + setmetatable(o, self) + self.__index = self + return o + end, + + -- + -- Parses a opaque string and creates a new Header instance + -- + -- @param data opaques string containing the raw data + -- @return hdr new instance of Header + parse = function(data) + local hdr = Header:new() + local pos + + pos, hdr.ver, hdr.func_id, hdr.pdu_len, hdr.flags, hdr.trans_id, + hdr.seq_id = bin.unpack(">SSSSSS", data) + + return hdr + end, + + -- + -- Converts the instance to an opaque string + -- @return str containing an opaque string + __tostring = function(self) + return bin.pack(">SSSSSS", self.ver, self.func_id, + self.pdu_len, self.flags, self.trans_id, self.seq_id ) + end + +} + +Attribute = { + + Tag = { + ISNS_TAG_DELIMITER = 0, + ISNS_TAG_ENTITY_IDENTIFIER = 1, + ISNS_TAG_ENTITY_PROTOCOL = 2, + ISNS_TAG_MGMT_IP_ADDRESS = 3, + ISNS_TAG_TIMESTAMP = 4, + ISNS_TAG_PROTOCOL_VERSION_RANGE = 5, + ISNS_TAG_REGISTRATION_PERIOD = 6, + ISNS_TAG_ENTITY_INDEX = 7, + ISNS_TAG_ENTITY_NEXT_INDEX = 8, + ISNS_TAG_ENTITY_ISAKMP_PHASE_1 = 11, + ISNS_TAG_ENTITY_CERTIFICATE = 12, + ISNS_TAG_PORTAL_IP_ADDRESS = 16, + ISNS_TAG_PORTAL_TCP_UDP_PORT = 17, + ISNS_TAG_PORTAL_SYMBOLIC_NAME = 18, + ISNS_TAG_ESI_INTERVAL = 19, + ISNS_TAG_ESI_PORT = 20, + ISNS_TAG_PORTAL_INDEX = 22, + ISNS_TAG_SCN_PORT = 23, + ISNS_TAG_PORTAL_NEXT_INDEX = 24, + ISNS_TAG_PORTAL_SECURITY_BITMAP = 27, + ISNS_TAG_PORTAL_ISAKMP_PHASE_1 = 28, + ISNS_TAG_PORTAL_ISAKMP_PHASE_2 = 29, + ISNS_TAG_PORTAL_CERTIFICATE = 31, + ISNS_TAG_ISCSI_NAME = 32, + ISNS_TAG_ISCSI_NODE_TYPE = 33, + ISNS_TAG_ISCSI_ALIAS = 34, + ISNS_TAG_ISCSI_SCN_BITMAP = 35, + ISNS_TAG_ISCSI_NODE_INDEX = 36, + ISNS_TAG_WWNN_TOKEN = 37, + ISNS_TAG_ISCSI_NODE_NEXT_INDEX = 38, + ISNS_TAG_ISCSI_AUTHMETHOD = 42, + ISNS_TAG_PG_ISCSI_NAME = 48, + ISNS_TAG_PG_PORTAL_IP_ADDR = 49, + ISNS_TAG_PG_PORTAL_TCP_UDP_PORT = 50, + ISNS_TAG_PG_TAG = 51, + ISNS_TAG_PG_INDEX = 52, + ISNS_TAG_PG_NEXT_INDEX = 53, + ISNS_TAG_FC_PORT_NAME_WWPN = 64, + ISNS_TAG_PORT_ID = 65, + ISNS_TAG_FC_PORT_TYPE = 66, + ISNS_TAG_SYMBOLIC_PORT_NAME = 67, + ISNS_TAG_FABRIC_PORT_NAME = 68, + ISNS_TAG_HARD_ADDRESS = 69, + ISNS_TAG_PORT_IP_ADDRESS = 70, + ISNS_TAG_CLASS_OF_SERVICE = 71, + ISNS_TAG_FC4_TYPES = 72, + ISNS_TAG_FC4_DESCRIPTOR = 73, + ISNS_TAG_FC4_FEATURES = 74, + ISNS_TAG_IFCP_SCN_BITMAP = 75, + ISNS_TAG_PORT_ROLE = 76, + ISNS_TAG_PERMANENT_PORT_NAME = 77, + ISNS_TAG_FC4_TYPE_CODE = 95, + ISNS_TAG_FC_NODE_NAME_WWNN = 96, + ISNS_TAG_SYMBOLIC_NODE_NAME = 97, + ISNS_TAG_NODE_IP_ADDRESS = 98, + ISNS_TAG_NODE_IPA = 99, + ISNS_TAG_PROXY_ISCSI_NAME = 101, + ISNS_TAG_SWITCH_NAME = 128, + ISNS_TAG_PREFERRED_ID = 129, + ISNS_TAG_ASSIGNED_ID = 130, + ISNS_TAG_VIRTUAL_FABRIC_ID = 131, + ISNS_TAG_SERVER_VENDOR_OUI = 256, + ISNS_TAG_DD_SET_ID = 2049, + ISNS_TAG_DD_SET_SYMBOLIC_NAME = 2050, + ISNS_TAG_DD_SET_STATUS = 2051, + ISNS_TAG_DD_SET_NEXT_ID = 2052, + ISNS_TAG_DD_ID = 2065, + ISNS_TAG_DD_SYMBOLIC_NAME = 2066, + ISNS_TAG_DD_MEMBER_ISCSI_INDEX = 2067, + ISNS_TAG_DD_MEMBER_ISCSI_NAME = 2068, + ISNS_TAG_DD_MEMBER_FC_PORT_NAME = 2069, + ISNS_TAG_DD_MEMBER_PORTAL_INDEX = 2070, + ISNS_TAG_DD_MEMBER_PORTAL_IP_ADDR = 2071, + ISNS_TAG_DD_MEMBER_PORTAL_TCP_UDP_PORT = 2072, + ISNS_TAG_DD_FEATURES = 2078, + ISNS_TAG_DD_NEXT_ID = 2079, + ISNS_VENDOR_SPECIFIC_SERVER_BASE = 257, + ISNS_VENDOR_SPECIFIC_ENTITY_BASE = 385, + ISNS_VENDOR_SPECIFIC_PORTAL_BASE = 513, + ISNS_VENDOR_SPECIFIC_NODE_BASE = 641, + ISNS_VENDOR_SPECIFIC_DD_BASE = 1024, + ISNS_VENDOR_SPECIFIC_DDSET_BASE = 1281, + ISNS_VENDOR_SPECIFIC_OTHER_BASE = 1537, + }, + + -- + -- Creates a new Attribute instance + -- + -- @param tag number containing the tag number + -- @param val string containing the tag value + -- @param len number containing the tag length + -- @return o new Attribute instance + new = function(self, tag, val, len) + local o = { tag = tag, len = ( len or (val and #val or 0) ), val = val or "" } + setmetatable(o, self) + self.__index = self + return o + end, + + -- + -- Creates a new Attribute instance + -- + -- @param data string containing an opaque string of raw data + -- @return attr new instance of Attribute + parse = function(data) + local attr = Attribute:new() + local pos + + pos, attr.tag, attr.len = bin.unpack(">II", data) + pos, attr.val = bin.unpack(">A" .. attr.len, pos) + + return attr + end, + + -- + -- Converts the instance to an opaque string + -- @return str containing an opaque string + __tostring = function(self) + return bin.pack(">IIA", self.tag, self.len, self.val) + end, + +} + +Attributes = { + + -- + -- Creates a new Attributes table + -- @return o new instance of Attributes + new = function(self) + local o = { attribs = {} } + setmetatable(o, self) + self.__index = self + return o + end, + + -- + -- Adds a new Attribute to the table + -- @param tag number containing the tag number + -- @param val string containing the tag value + -- @param len number containing the tag length + add = function(self, tag, val, len) + table.insert(self, Attribute:new(tag, val, len)) + end, + + -- + -- Converts the instance to an opaque string + -- @return str containing an opaque string + __tostring = function(self) + local str = "" + for _, attr in ipairs(self) do + str = str .. tostring(attr) + end + return str + end, + +} + +Request = { + + FuncId = { + DevAttrReg = 0x0001, + DevAttrQry = 0x0002, + DevGetNext = 0x0003, + DevDereg = 0x0004, + SCNReg = 0x0005, + SCNDereg = 0x0006, + SCNEvent = 0x0007, + SCN = 0x0008, + DDReg = 0x0009, + DDDereg = 0x000A, + DDSReg = 0x000B, + DDSDereg = 0x000C, + ESI = 0x000D, + Heartbeat = 0x000E, + }, + + -- + -- Creates a new Request message + -- @param func_id number containing the function ID of the message + -- @param flags number containing the message flags + -- @param data string containing the opaque raw data + -- @param auth string containing the opaqur raw auth data + -- @param trans_id number containing the transaction id + -- @param seq_id number containing the sequence id + -- @return o new instance of Request + new = function(self, func_id, flags, data, auth, trans_id, seq_id) + local o = { + header = Header:new(func_id, ( data and #data ) or 0, flags, ( trans_id or -1 ), ( seq_id or -1 )), + data = data or "" + } + setmetatable(o, self) + self.__index = self + return o + end, + + -- + -- Converts the instance to an opaque string + -- @return str containing an opaque string + __tostring = function(self) + return tostring(self.header) .. tostring(self.data) .. + ( self.auth and self.auth or "" ) + end, + + +} + +Response = { + + Error = { + [0] = "Successful", + [1] = "Unknown Error", + [2] = "Message Format Error", + [3] = "Invalid Registration", + [4] = "RESERVED", + [5] = "Invalid Query", + [6] = "Source Unknown", + [7] = "Source Absent", + [8] = "Source Unauthorized", + [9] = "No Such Entry", + [10] = "Version Not Supported", + [11] = "Internal Error", + [12] = "Busy", + [13] = "Option Not Understood", + [14] = "Invalid Update", + [15] = "Message (FUNCTION_ID) Not Supported", + [16] = "SCN Event Rejected", + [17] = "SCN Registration Rejected", + [18] = "Attribute Not Implemented", + [19] = "FC_DOMAIN_ID Not Available", + [20] = "FC_DOMAIN_ID Not Allocated", + [21] = "ESI Not Available", + [22] = "Invalid Deregistration", + [23] = "Registration Feature Not Supported", + }, + + -- + -- Creates a new Response instance + -- @return o new instance of Response + new = function(self) + local o = { attrs = Attributes:new() } + setmetatable(o, self) + self.__index = self + return o + end, + + -- + -- Creates a new Response instance + -- + -- @param data string containing an opaque string of raw data + -- @return attr new instance of Response + parse = function(data) + local hdr = Header.parse(data) + local pos = #(tostring(hdr)) + 1 + local resp = Response:new() + + pos, resp.error = bin.unpack(">I", data, pos) + if ( resp.error ~= 0 ) then + return resp + end + + while( pos < #data ) do + local tag, len, val + pos, tag, len = bin.unpack(">II", data, pos) + pos, val = bin.unpack("A" .. len, data, pos) + resp.attrs:add( tag, val, len ) + end + return resp + end, + +} + + +Session = { + + -- + -- Creates a new Session instance + -- @param host table + -- @param port table + -- @return o instance of Session + new = function(self, host, port) + local o = { + host = host, + port = port, + seq_id = 0, + trans_id = 0, + } + setmetatable(o, self) + self.__index = self + return o + end, + + -- + -- Connects to the server + -- @return status true on success, false on failure + connect = function(self) + self.socket = nmap.new_socket() + self.socket:set_timeout(5000) + return self.socket:connect(self.host, self.port) + end, + + -- + -- Sends data to the server + -- @return status true on success, false on failure + -- @return err string containing the error message on failure + send = function(self, req) + if ( not(req.header) or not(req.header.seq_id) or not(req.header.trans_id) ) then + return false, "Failed to send invalid request" + end + + -- update the sequence and transaction ID's + req.header.seq_id = self.seq_id + req.header.trans_id = self.trans_id + + local status, err = self.socket:send(tostring(req)) + self.trans_id = self.trans_id + 1 + + return status, err + end, + + -- + -- Receives data from the server + -- @return status true on success, false on failure + -- @return response instance of response + receive = function(self) + -- receive the 24 byte header + local status, buf_hdr = self.socket:receive_buf(match.numbytes(12), true) + if ( not(status) ) then + return status, buf_hdr + end + + local hdr = Header.parse(buf_hdr) + + -- receive the data + status, buf_data = self.socket:receive_buf(match.numbytes(hdr.pdu_len), true) + if ( not(status) ) then + return status, buf_data + end + + return true, Response.parse(buf_hdr .. buf_data) + end, + + close = function(self) + return self.close() + end +} + + +Helper = { + + -- + -- Creates a new Helper instance + -- @param host param + -- @param port param + -- @return o new instance of Helper + new = function(self, host, port) + local o = { session = Session:new(host, port) } + setmetatable(o, self) + self.__index = self + return o + end, + + -- + -- Connects to the server + -- @return status true on success, false on failure + connect = function(self) + return self.session:connect() + end, + + -- + -- Lists portals + -- @return status true on success, false on failure + -- @return resulst list of iSCSI nodes, err string on failure + listPortals = function(self) + local attribs, name = Attributes:new(), "iqn.control.node\0por" + + attribs:add(Attribute.Tag.ISNS_TAG_ISCSI_NAME, name) + attribs:add(Attribute.Tag.ISNS_TAG_PORTAL_IP_ADDRESS) + attribs:add(Attribute.Tag.ISNS_TAG_DELIMITER) + attribs:add(Attribute.Tag.ISNS_TAG_PORTAL_IP_ADDRESS) + attribs:add(Attribute.Tag.ISNS_TAG_PORTAL_TCP_UDP_PORT) + attribs:add(Attribute.Tag.ISNS_TAG_ENTITY_IDENTIFIER) + + local flags = 0x8c00 -- Sender is iSNS client, Last PDU, First PDU + + local req = Request:new(Request.FuncId.DevAttrQry, flags, tostring(attribs)) + if ( not(self.session:send(req)) ) then + return false, "Failed to send message to server" + end + + local status, resp = self.session:receive() + if ( not(status) ) then + return false, "Failed to receive message from server" + end + + local results = {} + local addr, proto, port + for _, attr in ipairs(resp.attrs) do + if ( attr.tag == Attribute.Tag.ISNS_TAG_PORTAL_IP_ADDRESS ) then + addr = attr.val + local pos, is_ipv4 = bin.unpack("A12", addr) + if ( is_ipv4 == "\0\0\0\0\0\0\0\0\0\0\xFF\xFF" ) then + local pos, bin_ip = bin.unpack("B4", addr, 13) + addr = ipops.bin_to_ip(bin_ip) + else + local pos, bin_ip = bin.unpack("B16", addr) + addr = ipops.bin_to_ip(bin_ip) + end + elseif ( attr.tag == Attribute.Tag.ISNS_TAG_PORTAL_TCP_UDP_PORT ) then + local pos, s1 + pos, s1, port = bin.unpack(">SS", attr.val) + + if ( s1 == 1 ) then + proto = "udp" + elseif ( s1 == 0 ) then + proto = "tcp" + else + proto = "UNKNOWN" + end + elseif ( addr and proto and port ) then + table.insert(results, { addr = addr, proto = proto, port = port } ) + addr, proto, port = nil, nil, nil + end + end + return true, results + end, + + -- + -- Lists iSCSI nodes + -- @return status true on success, false on failure + -- @return resulst list of iSCSI nodes, err string on failure + listISCINodes = function(self) + local attribs = Attributes:new() + local name = "iqn.control.node\0por" + attribs:add(Attribute.Tag.ISNS_TAG_ISCSI_NAME, name) + attribs:add(Attribute.Tag.ISNS_TAG_ISCSI_NAME) + attribs:add(Attribute.Tag.ISNS_TAG_DELIMITER) + attribs:add(Attribute.Tag.ISNS_TAG_ISCSI_NAME) + attribs:add(Attribute.Tag.ISNS_TAG_ISCSI_NODE_TYPE) + + local flags = 0x8c00 -- Sender is iSNS client, Last PDU, First PDU + + local req = Request:new(Request.FuncId.DevAttrQry, flags, tostring(attribs)) + if ( not(self.session:send(req)) ) then + return false, "Failed to send message to server" + end + + local status, resp = self.session:receive() + if ( not(status) ) then + return false, "Failed to receive message from server" + end + + local name, ntype + local results = {} + for _, attr in ipairs(resp.attrs) do + if ( attr.tag == Attribute.Tag.ISNS_TAG_ISCSI_NAME ) then + name = attr.val + elseif( attr.tag == Attribute.Tag.ISNS_TAG_ISCSI_NODE_TYPE ) then + local _, val = bin.unpack(">I", attr.val) + if ( val == iSCSI.NodeType.CONTROL ) then + ntype = "Control" + elseif ( val == iSCSI.NodeType.INITIATOR ) then + ntype = "Initiator" + elseif ( val == iSCSI.NodeType.TARGET ) then + ntype = "Target" + else + ntype = "Unknown" + end + end + if ( name and ntype ) then + table.insert(results, { name = name:match("^([^\0]*)"), type = ntype }) + name, ntype = nil, nil + end + end + return true, results + end, + + close = function(self) + return self.session:close() + end, + +} + +return _ENV; \ No newline at end of file diff --git a/scripts/isns-info.nse b/scripts/isns-info.nse new file mode 100644 index 000000000..a1bf54d86 --- /dev/null +++ b/scripts/isns-info.nse @@ -0,0 +1,70 @@ +local stdnse = require "stdnse" +local shortport = require "shortport" +local isns = require "isns" +local tab = require "tab" + +description = [[ +Lists portals and iSCSI nodes registered with the Internet Storage Name +Service (iSNS). +]] + +--- +-- @usage +-- nmap -p 3205 --script isns-info +-- +-- @output +-- PORT STATE SERVICE +-- 3205/tcp open unknown +-- | isns-info: +-- | Portal +-- | ip port +-- | 192.168.0.1 3260/tcp +-- | 192.168.0.2 3260/tcp +-- | iSCSI Nodes +-- | node type +-- | iqn.2001-04.com.example:storage.disk2.sys1.xyz Target +-- | iqn.2001-05.com.example:storage.disk2.sys1.xyz Target +-- |_ iqn.2001-04.a.com.example:storage.disk3.sys2.abc Target +-- + +portrule = shortport.port_or_service(3205, 'isns') + +author = "Patrik Karlsson" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"safe", "discovery"} + +local function fail(err) return ("\n ERROR: %s"):format(err or "") end + +action = function(host, port) + local helper = isns.Helper:new(host, port) + if ( not(helper:connect()) ) then + return fail("Failed to connect to server") + end + + local status, portals = helper:listPortals() + if ( not(status) ) then + return + end + + local results = {} + local restab = tab.new(2) + tab.addrow(restab, "ip", "port") + for _, portal in ipairs(portals) do + tab.addrow(restab, portal.addr, ("%d/%s"):format(portal.port, portal.proto)) + end + table.insert(results, { name = "Portal", tab.dump(restab) }) + + local status, nodes = helper:listISCINodes() + if ( not(status) ) then + return + end + + restab = tab.new(2) + tab.addrow(restab, "node", "type") + for _, portal in ipairs(nodes) do + tab.addrow(restab, portal.name, portal.type) + end + table.insert(results, { name = "iSCSI Nodes", tab.dump(restab) }) + + return stdnse.format_output(true, results) +end \ No newline at end of file diff --git a/scripts/script.db b/scripts/script.db index 5593e1555..48acac8d8 100644 --- a/scripts/script.db +++ b/scripts/script.db @@ -166,7 +166,7 @@ Entry { filename = "http-robtex-reverse-ip.nse", categories = { "discovery", "ex Entry { filename = "http-robtex-shared-ns.nse", categories = { "discovery", "external", "safe", } } Entry { filename = "http-title.nse", categories = { "default", "discovery", "safe", } } Entry { filename = "http-trace.nse", categories = { "discovery", "safe", "vuln", } } -Entry { filename = "http-traceroute.nse", categories = {"discovery", "safe", } } +Entry { filename = "http-traceroute.nse", categories = { "discovery", "safe", } } Entry { filename = "http-unsafe-output-escaping.nse", categories = { "discovery", "intrusive", } } Entry { filename = "http-userdir-enum.nse", categories = { "auth", "intrusive", } } Entry { filename = "http-vhosts.nse", categories = { "discovery", "intrusive", } } @@ -203,6 +203,7 @@ Entry { filename = "irc-info.nse", categories = { "default", "discovery", "safe" Entry { filename = "irc-unrealircd-backdoor.nse", categories = { "exploit", "intrusive", "malware", "vuln", } } Entry { filename = "iscsi-brute.nse", categories = { "brute", "intrusive", } } Entry { filename = "iscsi-info.nse", categories = { "default", "discovery", "safe", } } +Entry { filename = "isns-info.nse", categories = { "discovery", "safe", } } Entry { filename = "jdwp-version.nse", categories = { "version", } } Entry { filename = "krb5-enum-users.nse", categories = { "auth", "intrusive", } } Entry { filename = "ldap-brute.nse", categories = { "brute", "intrusive", } } @@ -289,6 +290,7 @@ Entry { filename = "rexec-brute.nse", categories = { "brute", "intrusive", } } Entry { filename = "riak-http-info.nse", categories = { "discovery", "safe", } } Entry { filename = "rlogin-brute.nse", categories = { "brute", "intrusive", } } Entry { filename = "rmi-dumpregistry.nse", categories = { "default", "discovery", "safe", } } +Entry { filename = "rmi-vuln-classloader.nse", categories = { "intrusive", "vuln", } } Entry { filename = "rpcap-brute.nse", categories = { "brute", "intrusive", } } Entry { filename = "rpcap-info.nse", categories = { "discovery", "safe", } } Entry { filename = "rpcinfo.nse", categories = { "default", "discovery", "safe", } }