diff options
Diffstat (limited to 'scripts/maxdb-info.nse')
-rw-r--r-- | scripts/maxdb-info.nse | 179 |
1 files changed, 179 insertions, 0 deletions
diff --git a/scripts/maxdb-info.nse b/scripts/maxdb-info.nse new file mode 100644 index 0000000..50d375a --- /dev/null +++ b/scripts/maxdb-info.nse @@ -0,0 +1,179 @@ +local nmap = require "nmap" +local shortport = require "shortport" +local stdnse = require "stdnse" +local string = require "string" +local stringaux = require "stringaux" +local tab = require "tab" +local table = require "table" + +description = [[ +Retrieves version and database information from a SAP Max DB database. +]] + +--- +-- @usage +-- nmap -p 7210 --script maxdb-info <ip> +-- +-- @output +-- PORT STATE SERVICE REASON +-- 7210/tcp open maxdb syn-ack +-- | maxdb-info: +-- | Version: 7.8.02 +-- | Build: DBMServer 7.8.02 Build 021-121-242-175 +-- | OS: UNIX +-- | Instroot: /opt/sdb/MaxDB +-- | Sysname: Linux 3.0.0-12-generic #20-Ubuntu SMP Fri Oct 7 14:56:25 UTC 2011 +-- | Databases +-- | instance path version kernel state +-- | MAXDB /opt/sdb/MaxDB 7.8.02.21 fast running +-- | MAXDB /opt/sdb/MaxDB 7.8.02.21 quick offline +-- | MAXDB /opt/sdb/MaxDB 7.8.02.21 slow offline +-- |_ MAXDB /opt/sdb/MaxDB 7.8.02.21 test offline +-- + +author = "Patrik Karlsson" +license = "Same as Nmap--See https://nmap.org/book/man-legal.html" +categories = { "default", "version", "safe" } + + +portrule = shortport.version_port_or_service(7210, "maxdb", "tcp") + +-- Sends and receive a MaxDB packet +-- @param socket already connected to the server +-- @param packet string containing the data to send +-- @return status true on success, false on failure +-- @return data string containing the raw response from the server +local function exchPacket(socket, packet) + local status, err = socket:send(packet) + if ( not(status) ) then + stdnse.debug2("Failed to send packet to server") + return false, "Failed to send packet to server" + end + + local data + status, data= socket:receive() + if ( not(status) ) then + stdnse.debug2("Failed to read packet from server") + return false, "Failed to read packet from server" + end + local len = string.unpack("<I2", data) + + -- make sure we've got it all + if ( len ~= #data ) then + local tmp + status, tmp = socket:receive_bytes(len - #data) + if ( not(status) ) then + stdnse.debug2("Failed to read packet from server") + return false, "Failed to read packet from server" + end + data = data .. tmp + end + return true, data +end + +-- Sends and receives a MaxDB command and does some very basic checks of the +-- response. +-- @param socket already connected to the server +-- @param packet string containing the data to send +-- @return status true on success, false on failure +-- @return data string containing the raw response from the server +local function exchCommand(socket, packet) + local status, data = exchPacket(socket, packet) + if( status ) then + if ( #data < 26 ) then + return false, "Response to short" + end + if ( "OK" ~= data:sub(25, 26) ) then + return false, "Incorrect response from server (no OK found)" + end + end + return status, data +end + +-- Parses and decodes the raw version response from the server +-- @param data string containing the raw response +-- @return version_info table containing a number of dynamic fields based on +-- the response from the server. The fields typically include: +-- <code>VERSION</code>, <code>BUILD</code>, <code>OS</code>, +-- <code>INSTROOT</code>,<code>LOGON</code>, <code>CODE</code>, +-- <code>SWAP</code>, <code>UNICODE</code>, <code>INSTANCE</code>, +-- <code>SYSNAME</code>, <code>MASKING</code>, +-- <code>REPLYTREATMENT</code> and <code>SDBDBM_IPCLOCATION</code> +local function parseVersion(data) + local version_info = {} + if ( #data > 27 ) then + for _, line in ipairs(stringaux.strsplit("\n", data:sub(28))) do + local key, val = line:match("^(%S+)%s-=%s(.*)%s*$") + if ( key ) then version_info[key] = val end + end + end + return version_info +end + +-- Parses and decodes the raw database response from the server +-- @param data string containing the raw response +-- @return result string containing a table of database instance information +local function parseDatabases(data) + local result = tab.new(5) + tab.addrow(result, "instance", "path", "version", "kernel", "state") + for _, line in ipairs(stringaux.strsplit("\n", data:sub(28))) do + local cols = {} + cols.instance, cols.path, cols.ver, cols.kernel, + cols.state = line:match("^(.-)%s*\t(.-)%s*\t(.-)%s*\t(.-)%s-\t(.-)%s-$") + if ( cols.instance ) then + tab.addrow(result, cols.instance, cols.path, cols.ver, cols.kernel, cols.state) + end + end + return tab.dump(result) +end + +local function fail (err) return stdnse.format_output(false, err) end + +action = function(host, port) + -- this could really be more elegant, but it has to do for now + local handshake = "5a000000035b000001000000ffffffff000004005a000000000242000409000000400000d03f00000040000070000000000500000004000000020000000300000749343231360004501c2a035201037201097064626d73727600" + local dbm_version = "28000000033f000001000000ac130000000004002800000064626d5f76657273696f6e2020202020" + local db_enum = "20000000033f000001000000ac130000000004002000000064625f656e756d20" + + local socket = nmap.new_socket() + socket:set_timeout(10000) + local status, err = socket:connect(host, port) + local data + + status, data = exchPacket(socket, stdnse.fromhex( handshake)) + if ( not(status) ) then + return fail("Failed to perform handshake with MaxDB server") + end + + status, data = exchPacket(socket, stdnse.fromhex( dbm_version)) + if ( not(status) ) then + return fail("Failed to request version information from server") + end + + local version_info = parseVersion(data) + if ( not(version_info) ) then + return fail("Failed to parse version information from server") + end + + local result, filter = {}, {"Version", "Build", "OS", "Instroot", "Sysname"} + for _, f in ipairs(filter) do + table.insert(result, ("%s: %s"):format(f, version_info[f:upper()])) + end + + status, data = exchCommand(socket, stdnse.fromhex( db_enum)) + socket:close() + if ( not(status) ) then + return fail("Failed to request version information from server") + end + local dbs = parseDatabases(data) + table.insert(result, { name = "Databases", dbs } ) + + -- set the version information + port.version.name = "maxdb" + port.version.product = "SAP MaxDB" + port.version.version = version_info.VERSION + port.version.ostype = version_info.SYSNAME + nmap.set_port_version(host, port) + + return stdnse.format_output(true, result) +end |