summaryrefslogtreecommitdiffstats
path: root/scripts/db2-das-info.nse
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/db2-das-info.nse')
-rw-r--r--scripts/db2-das-info.nse430
1 files changed, 430 insertions, 0 deletions
diff --git a/scripts/db2-das-info.nse b/scripts/db2-das-info.nse
new file mode 100644
index 0000000..22b305f
--- /dev/null
+++ b/scripts/db2-das-info.nse
@@ -0,0 +1,430 @@
+local nmap = require "nmap"
+local shortport = require "shortport"
+local stdnse = require "stdnse"
+local string = require "string"
+
+description = [[
+Connects to the IBM DB2 Administration Server (DAS) on TCP or UDP port 523 and
+exports the server profile. No authentication is required for this request.
+
+The script will also set the port product and version if a version scan is
+requested.
+]]
+
+-- rev 1.1 (2010-01-28)
+
+---
+-- @output
+-- PORT STATE SERVICE VERSION
+-- 523/tcp open ibm-db2 IBM DB2 Database Server 9.07.0
+-- | db2-das-info: DB2 Administration Server Settings
+-- | ;DB2 Server Database Access Profile
+-- | ;Use BINARY file transfer
+-- | ;Comment lines start with a ";"
+-- | ;Other lines must be one of the following two types:
+-- | ;Type A: [section_name]
+-- | ;Type B: keyword=value
+-- |
+-- | [File_Description]
+-- | Application=DB2/LINUX 9.7.0
+-- | Platform=18
+-- | File_Content=DB2 Server Definitions
+-- | File_Type=CommonServer
+-- | File_Format_Version=1.0
+-- | DB2System=MYBIGDATABASESERVER
+-- | ServerType=DB2LINUX
+-- |
+-- | [adminst>dasusr1]
+-- | NodeType=1
+-- | DB2Comm=TCPIP
+-- | Authentication=SERVER
+-- | HostName=MYBIGDATABASESERVER
+-- | PortNumber=523
+-- | IpAddress=127.0.1.1
+-- |
+-- | [inst>db2inst1]
+-- | NodeType=1
+-- | DB2Comm=TCPIP
+-- | Authentication=SERVER
+-- | HostName=MYBIGDATABASESERVER
+-- | ServiceName=db2c_db2inst1
+-- | PortNumber=50000
+-- | IpAddress=127.0.1.1
+-- | QuietMode=No
+-- | TMDatabase=1ST_CONN
+-- |
+-- | [db>db2inst1:TOOLSDB]
+-- | DBAlias=TOOLSDB
+-- | DBName=TOOLSDB
+-- | Drive=/home/db2inst1
+-- | Dir_entry_type=INDIRECT
+-- |_Authentication=NOTSPEC
+
+author = {"Patrik Karlsson", "Tom Sellers"}
+
+license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
+
+categories = {"safe", "discovery", "version"}
+
+
+--- Research Notes:
+--
+-- Little documentation on the protocol used to communicate with the IBM DB2 Admin Server
+-- service exists. The packets and methods here were developed based on data captured
+-- in the wild. Interviews with knowledgeable individuals indicates that the following
+-- information can be used to recreate the traffic.
+--
+-- Requirements:
+-- IBM DB2 Administrative Server (DAS) version >= 7.x instance, typically on port 523 tcp or udp
+-- IBM DB2 Control Center (Java application, workings on Linux, Windows, etc)
+--
+-- Steps to reproduce:
+-- Ensure network connectivity from test host to DB2 DAS instance on 523
+-- In the Control Center, right click on All Systems and click Add
+-- Enter the DB2 server IP or hostname in the System Name field and click OK
+-- Start packet capture
+-- Under All Systems right click on your DB2 server, choose export profile, enter file location, click OK
+-- Stop packet capture
+--
+-- Details on how to reproduce these steps with the CLI are welcome.
+
+portrule = shortport.version_port_or_service({523}, nil,
+ {"tcp","udp"},
+ {"open", "open|filtered"})
+
+--- Extracts the server profile from an already parsed db2 packet
+--
+-- This function assumes that the data contains the server profile and does
+-- no attempts to verify whether it does or not. The response from the function
+-- is simply a substring starting at offset 37.
+--
+-- @param data string containing the "info" section as parsed by parse_db2_packet
+-- @return string containing the complete server profile
+function extract_server_profile(data)
+
+ local server_profile_offset = 37
+
+ if server_profile_offset > data:len() then
+ return
+ end
+
+ return data:sub(server_profile_offset)
+
+end
+
+--- Does *very* basic parsing of a DB2 packet
+--
+-- Due to the limited documentation of the protocol this function is guesswork
+-- The section called info is essentially the data part of the db2das data response
+-- The length of this section is found at offset 158 in the db2das.data section
+--
+--
+-- @param packet table as returned from read_db2_packet
+-- @return table with parsed data
+function parse_db2_packet(packet)
+
+ local info_length_offset = 158
+ local info_offset = 160
+ local version_offset = 97
+ local response = {}
+
+ if packet.header.data_len < info_length_offset then
+ stdnse.debug1("packet too short to be DB2 response...")
+ return
+ end
+
+ local len = string.unpack(">I2", packet.data, info_length_offset)
+ response.version = string.unpack("z", packet.data, version_offset)
+ response.info_length = len - 4
+ response.info = packet.data:sub(info_offset, info_offset + response.info_length - (info_offset-info_length_offset))
+
+ if(nmap.debugging() > 3) then
+ stdnse.debug1("version: %s", response.version)
+ stdnse.debug1("info_length: %d", response.info_length)
+ stdnse.debug1("response.info:len(): %d", response.info:len())
+ end
+
+ return response
+
+end
+
+--- Reads a DB2 packet from the socket
+--
+-- Due to the limited documentation of the protocol this function is guesswork
+-- The first 41 bytes of the db2das response are considered to be the header
+-- The bytes following the header are considered to be the data
+--
+-- Offset 38 of the header contains an integer with the length of the data section
+-- The length of the data section can unfortunately be of either endianness
+-- There's
+--
+-- @param socket connected to the server
+-- @return table with header and data
+function read_db2_packet(socket)
+
+ local packet = {}
+ local header_len = 41
+ local total_len = 0
+ local buf
+
+ local DATA_LENGTH_OFFSET = 38
+ local ENDIANESS_OFFSET = 23
+
+ local catch = function()
+ stdnse.debug1("ERROR communicating with DB2 server")
+ socket:close()
+ end
+
+ local try = nmap.new_try(catch)
+ packet.header = {}
+
+ buf = try( socket:receive_bytes(header_len) )
+
+ packet.header.raw = buf:sub(1, header_len)
+
+ if packet.header.raw:sub(1, 10) == "\x00\x00\x00\x00\x44\x42\x32\x44\x41\x53" then
+
+ stdnse.debug1("Got DB2DAS packet")
+
+ local endian = string.unpack( "c2", packet.header.raw, ENDIANESS_OFFSET )
+
+ if endian == "9z" then
+ packet.header.data_len = string.unpack("<I4", packet.header.raw, DATA_LENGTH_OFFSET )
+ else
+ packet.header.data_len = string.unpack(">I4", packet.header.raw, DATA_LENGTH_OFFSET )
+ end
+
+ total_len = header_len + packet.header.data_len
+
+ if(nmap.debugging() > 3) then
+ stdnse.debug1("data_len: %d", packet.header.data_len)
+ stdnse.debug1("buf_len: %d", buf:len())
+ stdnse.debug1("total_len: %d", total_len)
+ end
+
+ -- do we have all data as specified by data_len?
+ while total_len > buf:len() do
+ -- if not read additional bytes
+ if(nmap.debugging() > 3) then
+ stdnse.debug1("Reading %d additional bytes", total_len - buf:len())
+ end
+ local tmp = try( socket:receive_bytes( total_len - buf:len() ) )
+ if(nmap.debugging() > 3) then
+ stdnse.debug1("Read %d bytes", tmp:len())
+ end
+ buf = buf .. tmp
+ end
+
+ packet.data = buf:sub(header_len + 1)
+
+ else
+ stdnse.debug1("Unknown packet, aborting ...")
+ return
+ end
+
+ return packet
+
+end
+
+--- Sends a db2 packet table over the wire
+--
+-- @param socket already connected to the server
+-- @param packet table as returned from <code>create_das_packet</code>
+--
+function send_db2_packet( socket, packet )
+
+ local catch = function()
+ stdnse.debug1("ERROR communicating with DB2 server")
+ socket:close()
+ end
+
+ local try = nmap.new_try(catch)
+
+ local buf = packet.header.raw .. packet.data
+
+ try( socket:send(buf) )
+
+end
+
+--- Creates a db2 packet table using the magic byte and data
+--
+-- The function returns a db2 packet table:
+-- packet.header - contains header specific values
+-- packet.header.raw - contains the complete un-parsed header (string)
+-- packet.header.data_len - contains the length of the data block
+-- packet.data - contains the complete un-parsed data block (string)
+--
+-- @param magic byte containing a value of unknown function (could be type)
+-- @param data string containing the db2 packet data
+-- @return table as described above
+--
+function create_das_packet( magic, data )
+
+ local packet = {}
+ local data_len = data:len()
+
+ packet.header = {}
+
+ packet.header.raw = "\x00\x00\x00\x00\x44\x42\x32\x44\x41\x53\x20\x20\x20\x20\x20\x20"
+ .. "\x01\x04\x00\x00\x00\x10\x39\x7a\x00\x05\x00\x00\x00\x00\x00\x00"
+ .. "\x00\x00\x00\x00"
+ .. string.pack("<B I2", magic, data_len)
+ .. "\x00\x00"
+
+ packet.header.data_len = data_len
+ packet.data = data
+
+ return packet
+end
+
+action = function(host, port)
+
+
+ -- create the socket used for our connection
+ local socket = nmap.new_socket()
+
+ -- set a reasonable timeout value
+ socket:set_timeout(10000)
+
+ -- do some exception handling / cleanup
+ local catch = function()
+ stdnse.debug1("ERROR communicating with " .. host.ip .. " on port " .. port.number)
+ socket:close()
+ end
+
+ local try = nmap.new_try(catch)
+
+
+ try(socket:connect(host, port))
+
+ local query
+
+ -- ************************************************************************************
+ -- Transaction block 1
+ -- ************************************************************************************
+ local data = "\x00\x00\x00\x0d\x00\x00\x00\x0c\x00\x00\x00\x4a\x00"
+
+ --try(socket:send(query))
+ local db2packet = create_das_packet(0x02, data)
+
+ send_db2_packet( socket, db2packet )
+ read_db2_packet( socket )
+
+ -- ************************************************************************************
+ -- Transaction block 2
+ -- ************************************************************************************
+ data = "\x00\x00\x00\x2c\x00\x00\x00"
+ .. "\x0c\x00\x00\x00\x08\x59\xe7\x1f\x4b\x79\xf0\x90\x72\x85\xe0\x8f"
+ .. "\x3e\x38\x45\x38\xe3\xe5\x12\xc4\x3b\xe9\x7d\xe2\xf5\xf0\x78\xcc"
+ .. "\x81\x6f\x87\x5f\x91"
+
+ db2packet = create_das_packet(0x05, data)
+
+ send_db2_packet( socket, db2packet )
+ read_db2_packet( socket )
+
+ -- ************************************************************************************
+ -- Transaction block 3
+ -- ************************************************************************************
+ data = "\x00\x00\x00\x0d\x00\x00\x00\x0c\x00\x00\x00\x4a\x01\x00\x00\x00"
+ .. "\x10\x00\x00\x00\x0c\x00\x00\x00\x4c\xff\xff\xff\xff\x00\x00\x00"
+ .. "\x20\x00\x00\x00\x0c\x00\x00\x00\x04\x00\x00\x04\xb8\x64\x62\x32"
+ .. "\x64\x61\x73\x4b\x6e\x6f\x77\x6e\x44\x73\x63\x76\x00\x00\x00\x00"
+ .. "\x20\x00\x00\x00\x0c\x00\x00\x00\x04\x00\x00\x04\xb8\x64\x62\x32"
+ .. "\x4b\x6e\x6f\x77\x6e\x44\x73\x63\x76\x53\x72\x76\x00"
+
+ db2packet = create_das_packet(0x0a, data)
+ send_db2_packet( socket, db2packet )
+ read_db2_packet( socket )
+
+ -- ************************************************************************************
+ -- Transaction block 4
+ -- ************************************************************************************
+ data = "\x00\x00\x00\x0d\x00\x00\x00\x0c\x00\x00\x00\x4a\x01\x00\x00\x00"
+ .. "\x20\x00\x00\x00\x0c\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x03"
+ .. "\x48\x00\x00\x00\x00\x4a\xfb\x42\x90\x00\x00\x24\x93\x00\x00\x00"
+ .. "\x10\x00\x00\x00\x0c\x00\x00\x00\x4c\xff\xff\xff\xff\x00\x00\x00"
+ .. "\x10\x00\x00\x00\x0c\x00\x00\x00\x4c\xff\xff\xff\xff\x00\x00\x00"
+ .. "\x20\x00\x00\x00\x0c\x00\x00\x00\x04\x00\x00\x04\xb8\x64\x62\x32"
+ .. "\x4b\x6e\x6f\x77\x6e\x44\x73\x63\x76\x53\x72\x76\x00\x00\x00\x00"
+ .. "\x20\x00\x00\x00\x0c\x00\x00\x00\x04\x00\x00\x04\xb8\x64\x62\x32"
+ .. "\x64\x61\x73\x4b\x6e\x6f\x77\x6e\x44\x73\x63\x76\x00\x00\x00\x00"
+ .. "\x0c\x00\x00\x00\x0c\x00\x00\x00\x04\x00\x00\x00\x10\x00\x00\x00"
+ .. "\x0c\x00\x00\x00\x4c\xff\xff\xff\xff\x00\x00\x00\x10\x00\x00\x00"
+ .. "\x0c\x00\x00\x00\x4c\xff\xff\xff\xff\x00\x00\x00\x11\x00\x00\x00"
+ .. "\x0c\x00\x00\x00\x04\x00\x00\x04\xb8\x00"
+
+ db2packet = create_das_packet(0x06, data)
+ send_db2_packet( socket, db2packet )
+
+ data = "\x00\x00\x00\x20\x00\x00\x00\x0c\x00\x00\x00\x04\x00"
+ .. "\x00\x04\xb8\x64\x62\x32\x64\x61\x73\x4b\x6e\x6f\x77\x6e\x44\x73"
+ .. "\x63\x76\x00\x00\x00\x00\x20\x00\x00\x00\x0c\x00\x00\x00\x04\x00"
+ .. "\x00\x04\xb8\x64\x62\x32\x4b\x6e\x6f\x77\x6e\x44\x73\x63\x76\x53"
+ .. "\x72\x76\x00\x00\x00\x00\x10\x00\x00\x00\x0c\x00\x00\x00\x4c\x00"
+ .. "\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x0c\x00\x00\x00\x4c\x00"
+ .. "\x00\x00\x01\x00\x00\x00\x10\x00\x00\x00\x0c\x00\x00\x00\x4c\x00"
+ .. "\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x0c\x00\x00\x00\x08\x00"
+ .. "\x00\x00\x10\x00\x00\x00\x0c\x00\x00\x00\x4c\x00\x00\x00\x01\x00"
+ .. "\x00\x00\x18\x00\x00\x00\x0c\x00\x00\x00\x08\x00\x00\x00\x0c\x00"
+ .. "\x00\x00\x0c\x00\x00\x00\x18"
+
+ db2packet = create_das_packet(0x06, data)
+ send_db2_packet( socket, db2packet )
+
+ local packet = read_db2_packet( socket )
+ local db2response = parse_db2_packet(packet)
+
+ socket:close()
+
+ -- The next block of code is essentially the version extraction code from db2-info.nse
+ local server_version
+ if string.sub(db2response.version,1,3) == "SQL" then
+ local major_version = string.sub(db2response.version,4,5)
+
+ -- strip the leading 0 from the major version, for consistency with
+ -- nmap-service-probes results
+ if string.sub(major_version,1,1) == "0" then
+ major_version = string.sub(major_version,2)
+ end
+ local minor_version = string.sub(db2response.version,6,7)
+ local hotfix = string.sub(db2response.version,8)
+ server_version = major_version .. "." .. minor_version .. "." .. hotfix
+ end
+
+ -- Try to determine which of the two values (probe version vs script) has more
+ -- precision. A couple DB2 versions send DB2 UDB 7.1 vs SQL090204 (9.02.04)
+ local _
+ local current_count = 0
+ if port.version.version ~= nil then
+ _, current_count = string.gsub(port.version.version, "%.", ".")
+ end
+
+ local new_count = 0
+ if server_version ~= nil then
+ _, new_count = string.gsub(server_version, "%.", ".")
+ end
+
+ if current_count < new_count then
+ port.version.version = server_version
+ end
+
+ local result = false
+
+ local db2profile = extract_server_profile( db2response.info )
+
+ if (db2profile ~= nil ) then
+ result = "DB2 Administration Server Settings\r\n"
+ .. extract_server_profile( db2response.info )
+
+ -- Set port information
+ port.version.name = "ibm-db2"
+ port.version.product = "IBM DB2 Database Server"
+ port.version.name_confidence = 10
+ nmap.set_port_version(host, port)
+ nmap.set_port_state(host, port, "open")
+ end
+
+ return result
+
+end