diff --git a/CHANGELOG b/CHANGELOG index a4af9bc03..7b0c8ef44 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,9 @@ # Nmap Changelog ($Id$); -*-text-*- +o [NSE] Added a Versant object database library and the scripts + broadcast-versant-locate and versant-info. The first discovers Versant + databases on the LAN and the second queries them for information. [Patrik] + o [NSE] Added the library rpcap and the scripts rpcap-brute and rpcap-info which perform brute force password guessing and extract information from the WinPcap Remote Packet Capture daemon. [Patrik] diff --git a/nselib/versant.lua b/nselib/versant.lua new file mode 100644 index 000000000..b3e004e72 --- /dev/null +++ b/nselib/versant.lua @@ -0,0 +1,279 @@ +--- +-- A tiny Versant library allowing some basic information enumeration. +-- The code is entirely based on packet dumps captured when using the Versant +-- Management Center administration application. +-- +-- @author "Patrik Karlsson " +-- + +module(... or "versant", package.seeall) + +require 'match' + +Versant = { + + -- fallback to these constants when version and user are not given + USER = "nmap", + VERSION = "8.0.2", + + -- Creates an instance of the Versant class + -- @param host table + -- @param port table + -- @return o new instance of Versant + new = function(self, host, port) + local o = { host = host, port = port, socket = nmap.new_socket() } + setmetatable(o, self) + self.__index = self + return o + end, + + -- Connects a socket to the Versant server + -- @return status true on success, false on failure + -- @return err string containing the error message if status is false + connect = function(self) + return self.socket:connect(self.host, self.port) + end, + + -- Closes the socket + -- @return status true on success, false on failure + -- @return err string containing the error message if status is false + close = function(self) + return self.socket:close() + end, + + -- Sends command to the server + -- @param cmd string containing the command to run + -- @param arg string containing any arguments + -- @param user [optional] string containing the user name + -- @param ver [optional] string containing the version number + -- @return status true on success, false on failure + -- @return data opaque string containing the response + sendCommand = function(self, cmd, arg, user, ver) + + user = user or Versant.USER + ver = ver or Versant.VERSION + arg = arg or "" + + local data = bin.pack("H", "000100000000000000020002000000010000000000000000000000000000000000010000") + data = data .. cmd .. "\0" .. user .. "\0" .. ver .. "\0" + -- align to even 4 bytes + if ( #data % 4 ~= 0 ) then + for i=1, ( 4 - (#data % 4)) do + data = data .. "\0" + end + end + + data = data .. bin.pack("H", "0000000b000001000000000000000000") + data = data .. ("%s:%d\0"):format(self.host.ip, self.port.number) + data = data .. "\0\0\0\0\0\0\0\0\0\0" .. arg + + while ( #data < 2048 ) do + data = data .. "\0" + end + + local status, err = self.socket:send(data) + if ( not(status) ) then + return false, "Failed to send request to server" + end + + local status, data = self.socket:receive_buf(match.numbytes(2048), true) + if ( not(status) ) then + return false, "Failed to read response from server" + end + + return status, data + end, + + -- Get database node information + -- @return status true on success, false on failure + -- @return result table containing an entry for each database. Each entry + -- contains a table with the following fields: + -- name - the database name + -- owner - the database owner + -- created - the date when the database was created + -- version - the database version + getNodeInfo = function(self) + local status, data = self:sendCommand("o_getnodeinfo", "-nodeinfo") + if ( not(status) ) then + return false, data + end + + status, data = self.socket:receive_buf(match.numbytes(4), true) + if ( not(status) ) then + return false, "Failed to read response from server" + end + + local _, db_count = bin.unpack(">I", data) + if ( db_count == 0 ) then + return false, "Database count was zero" + end + + status, data = self.socket:receive_buf(match.numbytes(4), true) + if ( not(status) ) then + return false, "Failed to read response from server" + end + + local _, buf_size = bin.unpack(">I", data) + local dbs = {} + + for i=1, db_count do + status, data = self.socket:receive_buf(match.numbytes(buf_size), true) + local _, db = nil, {} + + _, db.name = bin.unpack("z", data, 23) + _, db.owner = bin.unpack("z", data, 599) + _, db.created= bin.unpack("z", data, 631) + _, db.version= bin.unpack("z", data, 663) + + -- remove trailing line-feed + db.created = db.created:match("^(.-)\n*$") + + table.insert(dbs, db) + end + return true, dbs + end, + + -- Gets the database OBE port, this port is dynamically allocated once this + -- command completes. + -- + -- @return status true on success, false on failure + -- @return port table containing the OBE port + getObePort = function(self) + + local status, data = self:sendCommand("o_oscp", "-utility") + if ( not(status) ) then + return false, data + end + + status, data = self.socket:receive_buf(match.numbytes(256), true) + if ( not(status) ) then + return false, "Failed to read response from server" + end + + local pos, success = bin.unpack(">I", data) + if ( success ~= 0 ) then + return false, "Response contained invalid data" + end + + local port = { protocol = "tcp" } + pos, port.number = bin.unpack(">S", data, pos) + + return true, port + end, + + + -- Get's the XML license file from the database + -- @return status true on success, false on failure + -- @return data string containing the XML license file + getLicense = function(self) + + local status, data = self:sendCommand("o_licfile", "-license") + if ( not(status) ) then + return false, data + end + + status, data = self.socket:receive_buf(match.numbytes(4), true) + if ( not(status) ) then + return false, "Failed to read response from server" + end + + local _, len = bin.unpack(">I", data) + if ( len == 0 ) then + return false, "Failed to retrieve license file" + end + + status, data = self.socket:receive_buf(match.numbytes(len), true) + if ( not(status) ) then + return false, "Failed to read response from server" + end + + return true, data + end, + + -- Gets the TCP port for a given database + -- @param db string containing the database name + -- @return status true on success, false on failure + -- @return port table containing the database port + getDbPort = function(self, db) + local status, data = self:sendCommand(db, "") + if ( not(status) ) then + return false, data + end + + if ( not(status) ) then + return false, "Failed to connect to database" + end + + local _, port = nil, { protocol = "tcp" } + _, port.number = bin.unpack(">I", data, 27) + if ( port == 0 ) then + return false, "Failed to determine database port" + end + return true, port + end, +} + +Versant.OBE = { + + -- Creates a new versant OBE instance + -- @param host table + -- @param port table + -- @return o new instance of Versant OBE + new = function(self, host, port) + local o = { host = host, port = port, socket = nmap.new_socket() } + setmetatable(o, self) + self.__index = self + return o + end, + + -- Connects a socket to the Versant server + -- @return status true on success, false on failure + -- @return err string containing the error message if status is false + connect = function(self) + return self.socket:connect(self.host, self.port) + end, + + -- Closes the socket + -- @return status true on success, false on failure + -- @return err string containing the error message if status is false + close = function(self) + return self.socket:close() + end, + + -- Get database information including file paths and hostname + -- @return status true on success false on failure + -- @return result table containing the fields: + -- root_path - the database root directory + -- db_path - the database directory + -- lib_path - the library directory + -- hostname - the database host name + getVODInfo = function(self) + local data = bin.pack("H", "1002005d00000000000100000000000d000000000000000000000000") + data = data .. "-noprint -i " + + while( #data < 256 ) do + data = data .. "\0" + end + + self.socket:send(data) + local status, data = self.socket:receive_buf(match.numbytes(256), true) + if ( not(status) ) then + return false, "Failed to read response from server" + end + + local pos, len = bin.unpack(">I", data, 13) + status, data = self.socket:receive_buf(match.numbytes(len), true) + if ( not(status) ) then + return false, "Failed to read response from server" + end + + local result, pos, offset = {}, 1, 13 + pos, result.version = bin.unpack("z", data) + + for _, item in ipairs({"root_path", "db_path", "lib_path", "hostname"}) do + pos, result[item] = bin.unpack("z", data, offset) + offset = offset + 256 + end + return true, result + end, +} diff --git a/scripts/broadcast-versant-locate.nse b/scripts/broadcast-versant-locate.nse new file mode 100644 index 000000000..68c2fbaf6 --- /dev/null +++ b/scripts/broadcast-versant-locate.nse @@ -0,0 +1,35 @@ +description = [[ +Discovers Versant object databases using the srvloc protocol +]] + +--- +-- @usage +-- nmap --script broadcast-versant-locate +-- +-- @output +-- Pre-scan script results: +-- | broadcast-versant-locate: +-- |_ vod://192.168.200.222:5019 +-- + + +author = "Patrik Karlsson" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"broadcast", "safe"} + +require 'srvloc' + +prerule = function() return true end + +action = function() + local helper = srvloc.Helper:new() + local status, result = helper:ServiceRequest("service:odbms.versant:vod", "default") + helper:close() + + if ( not(status) ) then return end + local output = {} + for _, v in ipairs(result) do + table.insert(output, v:match("^service:odbms.versant:vod://(.*)$")) + end + return stdnse.format_output(true, output) +end \ No newline at end of file diff --git a/scripts/script.db b/scripts/script.db index 1b89348b1..987dac830 100644 --- a/scripts/script.db +++ b/scripts/script.db @@ -35,6 +35,7 @@ Entry { filename = "broadcast-rip-discover.nse", categories = { "broadcast", "sa Entry { filename = "broadcast-ripng-discover.nse", categories = { "broadcast", "safe", } } Entry { filename = "broadcast-sybase-asa-discover.nse", categories = { "broadcast", "safe", } } Entry { filename = "broadcast-upnp-info.nse", categories = { "broadcast", "safe", } } +Entry { filename = "broadcast-versant-locate.nse", categories = { "broadcast", "safe", } } Entry { filename = "broadcast-wake-on-lan.nse", categories = { "broadcast", "safe", } } Entry { filename = "broadcast-wpad-discover.nse", categories = { "broadcast", "safe", } } Entry { filename = "broadcast-wsdd-discover.nse", categories = { "broadcast", "safe", } } @@ -322,6 +323,7 @@ Entry { filename = "tftp-enum.nse", categories = { "discovery", "intrusive", } } Entry { filename = "unusual-port.nse", categories = { "safe", } } Entry { filename = "upnp-info.nse", categories = { "default", "discovery", "safe", } } Entry { filename = "url-snarf.nse", categories = { "safe", } } +Entry { filename = "versant-info.nse", categories = { "discovery", "safe", } } Entry { filename = "vmauthd-brute.nse", categories = { "brute", "intrusive", } } Entry { filename = "vnc-brute.nse", categories = { "brute", "intrusive", } } Entry { filename = "vnc-info.nse", categories = { "default", "discovery", "safe", } } diff --git a/scripts/versant-info.nse b/scripts/versant-info.nse new file mode 100644 index 000000000..bc2a1cd1b --- /dev/null +++ b/scripts/versant-info.nse @@ -0,0 +1,110 @@ +description = [[ +Extracts information, including file paths, version and database names from +a Versant object database. +]] + +--- +-- @usage +-- nmap -p 5019 --script versant-info +-- +-- @output +-- PORT STATE SERVICE REASON +-- 5019/tcp open versant syn-ack +-- | versant-info: +-- | Hostname: WIN-S6HA7RJFAAR +-- | Root path: C:\Versant\8 +-- | Database path: C:\Versant\db +-- | Library path: C:\Versant\8 +-- | Version: 8.0.2 +-- | Databases +-- | FirstDB@WIN-S6HA7RJFAAR:5019 +-- | Created: Sat Mar 03 12:00:02 2012 +-- | Owner: Administrator +-- | Version: 8.0.2 +-- | SecondDB@WIN-S6HA7RJFAAR:5019 +-- | Created: Sat Mar 03 03:44:10 2012 +-- | Owner: Administrator +-- | Version: 8.0.2 +-- | ThirdDB@WIN-S6HA7RJFAAR:5019 +-- | Created: Sun Mar 04 02:20:21 2012 +-- | Owner: Administrator +-- |_ Version: 8.0.2 +-- + +require 'shortport' +require 'versant' + +author = "Patrik Karlsson" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"discovery", "safe"} + +portrule = shortport.port_or_service(5019, "versant", "tcp") + +local function fail(err) return ("\n ERROR: %s"):format(err or "") end + +action = function(host, port) + + local v = versant.Versant:new(host, port) + local status = v:connect() + if ( not(status) ) then + return fail("Failed to connect to server") + end + + local status, newport = v:getObePort() + if ( not(status) ) then + return fail("Failed to retrieve OBE port") + end + v:close() + + v = versant.Versant.OBE:new(host, newport) + status = v:connect() + if ( not(status) ) then + return fail("Failed to connect to server") + end + + status, result = v:getVODInfo() + if ( not(status) ) then + return fail("Failed to get VOD information") + end + v:close() + + local output = {} + + table.insert(output, ("Hostname: %s"):format(result.hostname)) + table.insert(output, ("Root path: %s"):format(result.root_path)) + table.insert(output, ("Database path: %s"):format(result.db_path)) + table.insert(output, ("Library path: %s"):format(result.lib_path)) + table.insert(output, ("Version: %s"):format(result.version)) + + port.version.product = "Versant Database" + port.version.name = "versant" + nmap.set_port_version(host, port, "hardmatched") + + -- the script may fail after this part, but we want to report at least + -- the above information if that's the case. + + v = versant.Versant:new(host, port) + status = v:connect() + if ( not(status) ) then + return stdnse.format_output(true, output) + end + + status, result = v:getNodeInfo() + if ( not(status) ) then + return stdnse.format_output(true, output) + end + v:close() + + local databases = { name = "Databases" } + + for _, db in ipairs(result) do + local db_tbl = { name = db.name } + table.insert(db_tbl, ("Created: %s"):format(db.created)) + table.insert(db_tbl, ("Owner: %s"):format(db.owner)) + table.insert(db_tbl, ("Version: %s"):format(db.version)) + table.insert(databases, db_tbl) + end + + table.insert(output, databases) + return stdnse.format_output(true, output) +end \ No newline at end of file