summaryrefslogtreecommitdiffstats
path: root/scripts/maxdb-info.nse
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/maxdb-info.nse')
-rw-r--r--scripts/maxdb-info.nse179
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