From 8f7461b357b876bee6438002cd7af0a370a17f68 Mon Sep 17 00:00:00 2001 From: patrik Date: Sun, 19 Feb 2012 14:56:17 +0000 Subject: [PATCH] o [NSE] Added a Network Data Management Protocol (ndmp) library and the scripts: + ndmp-version - retrieves version information + ndmp-fs-info - retrieves information about remote filesystems [Patrik] --- CHANGELOG | 6 + nselib/ndmp.lua | 405 +++++++++++++++++++++++++++++++++++++++ scripts/ndmp-fs-info.nse | 69 +++++++ scripts/ndmp-version.nse | 61 ++++++ scripts/script.db | 2 + 5 files changed, 543 insertions(+) create mode 100644 nselib/ndmp.lua create mode 100644 scripts/ndmp-fs-info.nse create mode 100644 scripts/ndmp-version.nse diff --git a/CHANGELOG b/CHANGELOG index 0995c2a78..7e1503c4f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,11 @@ # Nmap Changelog ($Id$); -*-text-*- +o [NSE] Added a Network Data Management Protocol (ndmp) library and the + scripts: + + ndmp-version - retrieves version information + + ndmp-fs-info - retrieves information about remote filesystems + [Patrik] + o [NSE] Added the script http-vuln-cve2010-2861 to detect the Cold Fusion CVE-2010-2861 directory traversal vulnerability. [Micah Hoffman] diff --git a/nselib/ndmp.lua b/nselib/ndmp.lua new file mode 100644 index 000000000..128677e83 --- /dev/null +++ b/nselib/ndmp.lua @@ -0,0 +1,405 @@ +--- +-- A minimalistic ndmp library +-- +-- @author Patrik Karlsson +-- + +module(... or "ndmp",package.seeall) + +require 'match' + +NDMP = { + + -- Message types + MessageType = { + CONFIG_GET_HOST_INFO = 0x00000100, + CONFIG_GET_FS_INFO = 0x00000105, + CONFIG_GET_AUTH_ATTR = 0x00000103, + CONFIG_GET_SERVER_INFO = 0x00000108, + CONNECT_CLIENT_AUTH = 0x00000901, + }, + + -- The fragment header, 4 bytes where the highest bit is used to determine + -- whether the fragment is the last or not. + FragmentHeader = { + size = 4, + + -- Creates a new instance of fragment header + -- @return o instance of Class + new = function(self) + local o = { + last = true, + length = 24, + } + setmetatable(o, self) + self.__index = self + return o + end, + + -- Parse data stream and create a new instance of this class + -- @param data opaque string + -- @return fh new instance of FragmentHeader class + parse = function(data) + local fh = NDMP.FragmentHeader:new() + local _, tmp = bin.unpack(">I", data) + fh.length = bit.band(tmp, 0x7fffffff) + fh.last= bit.rshift(tmp, 31) + return fh + end, + + -- Serializes the instance to an opaque string + -- @return str string containing the serialized class + __tostring = function(self) + local tmp = 0 + if ( self.last ) then + tmp = 0x80000000 + end + tmp = tmp + self.length + return bin.pack(">I", tmp) + end, + + }, + + -- The ndmp 24 byte header + Header = { + size = 24, + + -- creates a new instance of Header + -- @return o instance of Header + new = function(self) + local o = { + seq = 0, + time = os.time(), + type = 0, + msg = 0x00000108, + reply_seq = 0, + error = 0, + } + setmetatable(o, self) + self.__index = self + return o + end, + + -- Create a Header instance from opaque data string + -- @param data opaque string + -- @return hdr new instance of Header + parse = function(data) + local hdr = NDMP.Header:new() + local pos + pos, hdr.seq, hdr.time, hdr.type, hdr.msg, hdr.reply_seq, hdr.error = bin.unpack(">IIIIII", data) + return hdr + end, + + -- Serializes the instance to an opaque string + -- @return str string containing the serialized class instance + __tostring = function(self) + return bin.pack(">IIIIII", self.seq, self.time, self.type, self.msg, self.reply_seq, self.error) + end, + + }, +} + +NDMP.Message = {} + +NDMP.Message.ConfigGetServerInfo = { + + -- Creates a Config Server Info instance + -- @return o new instance of Class + new = function(self) + local o = { + frag_header = NDMP.FragmentHeader:new(), + header = NDMP.Header:new(), + data = nil, + } + o.header.msg = NDMP.MessageType.CONFIG_GET_SERVER_INFO + setmetatable(o, self) + self.__index = self + return o + end, + + -- Create a ConfigGetServerInfo instance from opaque data string + -- @param data opaque string + -- @return msg new instance of ConfigGetServerInfo + parse = function(data) + local msg = NDMP.Message.ConfigGetServerInfo:new() + msg.frag_header = NDMP.FragmentHeader.parse(data) + data = data:sub(NDMP.FragmentHeader.size + 1) + msg.header = NDMP.Header.parse(data) + msg.data = data:sub(NDMP.Header.size + 1) + + msg.serverinfo = {} + local pos, err = bin.unpack(">I", msg.data) + pos, msg.serverinfo.vendor = Util.parseString(msg.data, pos) + pos, msg.serverinfo.product = Util.parseString(msg.data, pos) + pos, msg.serverinfo.version = Util.parseString(msg.data, pos) + return msg + end, + + -- Serializes the instance to an opaque string + -- @return str string containing the serialized class instance + __tostring = function(self) + return tostring(self.frag_header) .. tostring(self.header) .. tostring(self.data or "") + end, + +} + +NDMP.Message.ConfigGetHostInfo = { + new = function(self) + local o = { + frag_header = NDMP.FragmentHeader:new(), + header = NDMP.Header:new(), + data = nil, + } + o.header.msg = NDMP.MessageType.CONFIG_GET_HOST_INFO + setmetatable(o, self) + self.__index = self + return o + end, + + parse = function(data) + local msg = NDMP.Message.ConfigGetServerInfo:new() + msg.frag_header = NDMP.FragmentHeader.parse(data) + data = data:sub(NDMP.FragmentHeader.size + 1) + msg.header = NDMP.Header.parse(data) + msg.data = data:sub(NDMP.Header.size + 1) + + msg.hostinfo = {} + local pos, err = bin.unpack(">I", msg.data) + pos, msg.hostinfo.hostname = Util.parseString(msg.data, pos) + pos, msg.hostinfo.ostype = Util.parseString(msg.data, pos) + pos, msg.hostinfo.osver = Util.parseString(msg.data, pos) + pos, msg.hostinfo.hostid = Util.parseString(msg.data, pos) + return msg + end, + + __tostring = function(self) + return tostring(self.frag_header) .. tostring(self.header) .. tostring(self.data or "") + end, +} + +NDMP.Message.ConfigGetFsInfo = { + + new = function(self) + local o = { + frag_header = NDMP.FragmentHeader:new(), + header = NDMP.Header:new(), + data = nil, + fsinfo = {}, + } + o.header.msg = NDMP.MessageType.CONFIG_GET_FS_INFO + setmetatable(o, self) + self.__index = self + return o + end, + + parse = function(data) + local msg = NDMP.Message.ConfigGetFsInfo:new() + msg.frag_header = NDMP.FragmentHeader.parse(data) + data = data:sub(NDMP.FragmentHeader.size + 1) + msg.header = NDMP.Header.parse(data) + msg.data = data:sub(NDMP.Header.size + 1) + + local pos, err, count = bin.unpack(">II", msg.data) + for i=1, count do + local item = {} + pos, item.invalid = bin.unpack(">I", msg.data, pos) + pos, item.fs_type = Util.parseString(msg.data, pos) + pos, item.fs_logical_device = Util.parseString(msg.data, pos) + pos, item.fs_physical_device = Util.parseString(msg.data, pos) + pos, item.total_size = bin.unpack(">L", msg.data, pos) + pos, item.used_size = bin.unpack(">L", msg.data, pos) + pos, item.avail_size = bin.unpack(">L", msg.data, pos) + pos, item.total_inodes = bin.unpack(">L", msg.data, pos) + pos, item.used_inodes = bin.unpack(">L", msg.data, pos) + pos, item.fs_env = Util.parseString(msg.data, pos) + pos, item.fs_status = Util.parseString(msg.data, pos) + table.insert(msg.fsinfo, item) + end + return msg + end, + + __tostring = function(self) + return tostring(self.frag_header) .. tostring(self.header) .. tostring(self.data or "") + end, +} + +NDMP.Message.UnhandledMessage = { + + new = function(self) + local o = { + frag_header = NDMP.FragmentHeader:new(), + header = NDMP.Header:new(), + data = nil, + } + setmetatable(o, self) + self.__index = self + return o + end, + + parse = function(data) + local msg = NDMP.Message.ConfigGetFsInfo:new() + msg.frag_header = NDMP.FragmentHeader.parse(data) + data = data:sub(NDMP.FragmentHeader.size + 1) + msg.header = NDMP.Header.parse(data) + msg.data = data:sub(NDMP.Header.size + 1) + return msg + end, + + __tostring = function(self) + return tostring(self.frag_header) .. tostring(self.header) .. tostring(self.data or "") + end + +} + +Util = { + + parseString = function(data, pos) + local pos, str = bin.unpack(">a", data, pos) + local pad = ( 4 - ( #str % 4 ) ~= 4 ) and 4 - ( #str % 4 ) or 0 + return pos + pad, str + + end, + +} + +NDMP.TypeToMessage = { + [NDMP.MessageType.CONFIG_GET_SERVER_INFO] = NDMP.Message.ConfigGetServerInfo, + [NDMP.MessageType.CONFIG_GET_HOST_INFO] = NDMP.Message.ConfigGetHostInfo, + [NDMP.MessageType.CONFIG_GET_FS_INFO] = NDMP.Message.ConfigGetFsInfo, +} + +-- Handles the communication with the NDMP service +Comm = { + + -- Creates new Comm instance + -- @param host table as received by the action method + -- @param port table as receuved by the action method + -- @return o new instance of Comm + new = function(self, host, port) + local o = { + host = host, + port = port, + socket = nmap.new_socket(), + seq = 0, + in_queue = {}, + } + setmetatable(o, self) + self.__index = self + return o + end, + + -- Connects to the NDMP server + -- @return status true on success, false on failure + connect = function(self) + -- some servers seem to take their time, so leave this as 10s for now + self.socket:set_timeout(10000) + return self.socket:connect(self.host, self.port) + end, + + -- Receives a message from the server + -- @return status true on success, false on failure + -- @return msg NDMP message when a parser exists, otherwise nil + sock_recv = function(self) + local status, frag_data = self.socket:receive_buf(match.numbytes(4), true) + if ( not(status) ) then + return false, "Failed to read NDMP 4-byte fragment header" + end + local frag_header = NDMP.FragmentHeader.parse(frag_data) + + local status, header_data = self.socket:receive_buf(match.numbytes(24), true) + if ( not(status) ) then + return false, "Failed to read NDMP 24-byte header" + end + local header = NDMP.Header.parse(header_data) + + local status, data = self.socket:receive_buf(match.numbytes(frag_header.length - 24), true) + if ( not(status) ) then + return false, "Failed to read NDMP data" + end + + if ( NDMP.TypeToMessage[header.msg] ) then + return true, NDMP.TypeToMessage[header.msg].parse(frag_data .. header_data .. data) + end + return true, NDMP.Message.UnhandledMessage.parse(frag_data .. header_data .. data) + end, + + recv = function(self) + if ( #self.in_queue > 0 ) then + return true, table.remove(self.in_queue, 1) + end + return self:sock_recv() + end, + + -- Sends a message to the server + -- @param msg NDMP message + -- @return status true on success, false on failure + -- @return err string containing the error message when status is false + send = function(self, msg) + self.seq = self.seq + 1 + msg.header.seq = self.seq + return self.socket:send(tostring(msg)) + end, + + + exch = function(self, msg) + local status, err = self:send(msg) + if ( not(status) ) then + return false, "Failed to send ndmp Message to server" + end + local s_seq = msg.header.seq + + for k, v in ipairs(self.in_queue) do + if ( v.reply_seq == s_seq ) then + return true, table.remove(self.in_queue, k) + end + end + + while(true) do + local reply + status, reply = self:sock_recv() + if ( not(status) ) then + return false, "Failed to receive msg from server" + elseif ( reply and reply.header and reply.header.reply_seq == s_seq ) then + return true, reply + else + table.insert(self.in_queue, reply) + end + end + end, + + close = function(self) return self.socket:close() end, + +} + + +Helper = { + + new = function(self, host, port) + local o = { comm = Comm:new(host, port) } + setmetatable(o, self) + self.__index = self + return o + end, + + connect = function(self) + return self.comm:connect() + end, + + getFsInfo = function(self) + return self.comm:exch(ndmp.NDMP.Message.ConfigGetFsInfo:new()) + end, + + getHostInfo = function(self) + return self.comm:exch(ndmp.NDMP.Message.ConfigGetHostInfo:new()) + end, + + getServerInfo = function(self) + return self.comm:exch(ndmp.NDMP.Message.ConfigGetServerInfo:new()) + end, + + close = function(self) + return self.comm:close() + + end + +} diff --git a/scripts/ndmp-fs-info.nse b/scripts/ndmp-fs-info.nse new file mode 100644 index 000000000..b68ad209b --- /dev/null +++ b/scripts/ndmp-fs-info.nse @@ -0,0 +1,69 @@ +description = [[ +Lists remote file systems by querying the remote device using the Network +Data Management Protocol (ndmp). NDMP is a protocol intended to transport +data between a NAS device and the backup device, removing the need for the +data to pass through the backup server. The following products are known +to support the protocol: +* Amanda +* Bacula +* CA Arcserve +* CommVault Simpana +* EMC Networker +* Hitachi Data Systems +* IBM Tivoli +* Quest Software Netvault Backup +* Symantec Netbackup +* Symantec Backup Exec +]] + +--- +-- @usage +-- nmap -p 10000 --script ndmp-fs-info +-- +-- @output +-- PORT STATE SERVICE REASON VERSION +-- 10000/tcp open ndmp syn-ack Symantec/Veritas Backup Exec ndmp +-- | ndmp-fs-info: +-- | FS Logical device Physical device +-- | NTFS C: Device0000 +-- | NTFS E: Device0000 +-- | UNKNOWN Shadow Copy Components Device0000 +-- |_UNKNOWN System State Device0000 +-- +-- + +author = "Patrik Karlsson" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"discovery", "safe"} + +require 'shortport' +require 'ndmp' +require 'tab' + +portrule = shortport.port_or_service(10000, "ndmp", "tcp") + +local function fail(err) return ("\n ERROR: %s"):format(err or "") end + +action = function(host, port) + + local helper = ndmp.Helper:new(host, port) + local status, msg = helper:connect() + if ( not(status) ) then return fail("Failed to connect to server") end + + status, msg = helper:getFsInfo() + if ( not(status) ) then return fail("Failed to get filesystem information from server") end + helper:close() + + local result = tab.new(3) + tab.addrow(result, "FS", "Logical device", "Physical device") + + for _, item in ipairs(msg.fsinfo) do + if ( item.fs_logical_device and #item.fs_logical_device ~= 0 ) then + if ( item and item.fs_type and item.fs_logical_device and item.fs_physical_device ) then + tab.addrow(result, item.fs_type, item.fs_logical_device:gsub("?", " "), item.fs_physical_device) + end + end + end + + return "\n" .. tab.dump(result) +end \ No newline at end of file diff --git a/scripts/ndmp-version.nse b/scripts/ndmp-version.nse new file mode 100644 index 000000000..13b9c41e5 --- /dev/null +++ b/scripts/ndmp-version.nse @@ -0,0 +1,61 @@ +description = [[ +Retrieves version information from the remote Network Data Management Protocol +(ndmp) service. NDMP is a protocol intended to transport data between a NAS +device and the backup device, removing the need for the data to pass through +the backup server. The following products are known to support the protocol: +* Amanda +* Bacula +* CA Arcserve +* CommVault Simpana +* EMC Networker +* Hitachi Data Systems +* IBM Tivoli +* Quest Software Netvault Backup +* Symantec Netbackup +* Symantec Backup Exec +]] + +author = "Patrik Karlsson" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"version"} + +require 'shortport' +require 'ndmp' + +portrule = shortport.port_or_service(10000, "ndmp", "tcp") + +local function fail(err) return ("\n ERROR: %s"):format(err or "") end + +local function vendorLookup(vendor) + if ( vendor:match("VERITAS") ) then + return "Symantec/Veritas Backup Exec ndmp" + else + return vendor + end +end + +action = function(host, port) + local helper = ndmp.Helper:new(host, port) + local status, err = helper:connect() + if ( not(status) ) then return fail("Failed to connect to server") end + + local hi, si + status, hi = helper:getHostInfo() + if ( not(status) ) then return fail("Failed to get host information from server") end + + status, si = helper:getServerInfo() + if ( not(status) ) then return fail("Failed to get server information from server") end + helper:close() + + local major, minor, build, smajor, sminor = hi.hostinfo.osver:match("Major Version=(%d+) Minor Version=(%d+) Build Number=(%d+) ServicePack Major=(%d+) ServicePack Minor=(%d+)") + port.version.name = "ndmp" + port.version.product = vendorLookup(si.serverinfo.vendor) + port.version.ostype = hi.hostinfo.ostype + if ( hi.hostinfo.hostname ) then + port.version.extrainfo = ("Name: %s; "):format(hi.hostinfo.hostname) + end + if ( major and minor and build and smajor and sminor ) then + port.version.extrainfo = port.version.extrainfo .. ("OS ver: %d.%d; OS Build: %d; OS Service Pack: %d"):format(major, minor, build, smajor) + end + nmap.set_port_version(host, port, "hardmatched") +end \ No newline at end of file diff --git a/scripts/script.db b/scripts/script.db index f80b20134..31abfe828 100644 --- a/scripts/script.db +++ b/scripts/script.db @@ -205,6 +205,8 @@ Entry { filename = "nat-pmp-mapport.nse", categories = { "discovery", "safe", } Entry { filename = "nbstat.nse", categories = { "default", "discovery", "safe", } } Entry { filename = "ncp-enum-users.nse", categories = { "auth", "safe", } } Entry { filename = "ncp-serverinfo.nse", categories = { "default", "discovery", "safe", } } +Entry { filename = "ndmp-fs-info.nse", categories = { "discovery", "safe", } } +Entry { filename = "ndmp-version.nse", categories = { "version", } } Entry { filename = "nessus-brute.nse", categories = { "brute", "intrusive", } } Entry { filename = "nessus-xmlrpc-brute.nse", categories = { "brute", "intrusive", } } Entry { filename = "netbus-auth-bypass.nse", categories = { "auth", "safe", "vuln", } }