1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 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
|