diff options
Diffstat (limited to '')
-rw-r--r-- | scripts/db2-das-info.nse | 430 |
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 |