diff --git a/CHANGELOG b/CHANGELOG
index 8d49fdc0f..2faaefcb6 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,10 @@
# Nmap Changelog ($Id$); -*-text-*-
+o [NSE] Add new DB2 library and two scripts
+ - db2-brute.nse uses the unpwdb library to guess credentials for DB2
+ - db2-info.nse re-write of Tom Sellers script to use the new library
+ [Patrik]
+
o [Ncat] In listen mode, the --exec and --sh-exec options now accept a
single connection and then exit, just like in normal listen mode.
Use the --keep-open option to get the old default inetd-like
diff --git a/nselib/db2.lua b/nselib/db2.lua
new file mode 100644
index 000000000..2b2fe1274
--- /dev/null
+++ b/nselib/db2.lua
@@ -0,0 +1,834 @@
+---
+-- DB2 Library supporting a very limited subset of operations
+--
+-- Summary
+-- -------
+-- o The library currently provides functionality to:
+-- 1. Query the server for basic settings using the
+-- getServerInfo function of the helper class
+-- 2. Authenticate to a DB2 server using a plain-text username and
+-- password.
+--
+-- Overview
+-- --------
+-- The library contains the following classes:
+--
+-- o DRDA
+-- - Implements the Distributed Relational Database Architecture class
+--
+-- o DRDAParameter
+-- - Implements a number of functions to handle DRDA parameters
+--
+-- o DDM
+-- - Implements the DDM portion of the DRDA structure
+--
+-- o Command
+-- - Provides functions for easy creation of the most common DRDA's
+-- - Implemented as a static class that returns an instance of the DRDA
+--
+-- o Helper
+-- - A helper class that provides easy access to the rest of the library
+--
+-- o DB2Socket
+-- - A smallish socket wrapper that provides fundamental buffering
+--
+-- o StringUtil
+-- - Provides EBCDIC/ASCII conversion functions
+--
+--
+-- Example
+-- -------
+-- The following sample code illustrates how scripts can use the Helper class
+-- to interface the library:
+--
+--
+-- db2helper = db2.Helper:new()
+-- status, err = db2helper:connect(host, port)
+-- status, res = db2helper:getServerInfo()
+-- status, err = db2helper:close()
+--
+--
+-- Additional information
+-- ----------------------
+-- The implementation is based on packet dumps and the excellent decoding
+-- provided by Wireshark.
+--
+-- There is some documentation over at:
+-- o http://publib.boulder.ibm.com/infocenter/dzichelp/v2r2/topic/
+-- com.ibm.db29.doc.drda/db2z_drda.htm [link spans two lines]
+--
+-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
+-- @author "Patrik Karlsson "
+--
+
+--
+-- Version 0.1
+-- Created 05/08/2010 - v0.1 - created by Patrik Karlsson
+--
+
+module(... or "db2", package.seeall)
+
+require "bin"
+
+-- CodePoint constants
+CodePoint = {
+ TYPDEFNAM = 0x002f,
+ TYPDEFOVR = 0x0035,
+ ACCSEC = 0x106d,
+ SECCHK = 0x106e,
+ EXCSAT = 0x1041,
+ PRDID = 0x112e,
+ SRVCLSNM = 0x1147,
+ SRVRLSLV = 0x115a,
+ EXTNAM = 0x115e,
+ SRVNAM = 0x116d,
+ USRID = 0x11a0,
+ PASSWORD = 0x11a1,
+ SECMEC = 0x11a2,
+ SECCHKCD = 0x11a4,
+ MGRLVLLS = 0x1404,
+ EXCSATRD = 0x1443,
+ ACCRDB = 0x2001,
+ PRDDATA = 0x2104,
+ RDBACCL = 0x210f,
+ RDBNAM = 0x2110,
+ RDBNFNRM = 0x2211,
+ RDBAFLRM = 0x221a,
+}
+
+-- Security Mechanism
+SecMec =
+{
+ USER_PASSWORD = 0x0003,
+ USER_ONLY = 0x0004,
+ CHANGE_PASSWORD = 0x0005,
+ USER_PASS_SUBST = 0x0006,
+ USER_ENC_PASS = 0x0007,
+ ENC_USER_ENC_PASS = 0x0009,
+ ENC_CHANGE_PASS = 0x000A,
+ KERBEROS = 0x000B,
+ ENC_USER_DATA = 0x000C,
+ ENC_USER_ENC_PASS_ENC_DATA = 0x000D,
+ ENC_USER_ENC_PASS_ENC_NEWPASS_ENC_DATA = 0x000E,
+}
+
+-- Distributed Relational Database Architecture (DRDA) Class
+DRDA = {
+
+ new = function(self, ddm)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.Parameters = {}
+ o.DDM = ddm
+ return o
+ end,
+
+ --- Sets the DDM
+ --
+ -- @param ddm DDM to assign to the DRDA
+ -- @return status boolean true on success, false on failure
+ setDDM = function( self, ddm )
+ if ( not(ddm) ) then
+ return false, "DDM cannot be nil"
+ end
+ self.DDM = ddm
+ return true
+ end,
+
+ --- Adds a DRDA parameter to the table
+ --
+ -- @param param DRDAParam containing the parameter to add to the table
+ -- @return status bool true on success, false on failure
+ -- @return err string containing the error message if status is false
+ addParameter = function( self, param )
+ if ( not(self.DDM) ) then
+ stdnse.print_debug("db2.DRDA.addParameter: DDM must be set prior to adding parameters")
+ return false, "DDM must be set prior to adding parameters"
+ end
+ if ( not(param) ) then
+ stdnse.print_debug("db2.DRDA.addParameter: Param cannot be nil")
+ return false, "Param cannot be nil"
+ end
+
+ table.insert(self.Parameters, param)
+
+ -- update the DDM length fields
+ self.DDM.Length = self.DDM.Length + param.Length
+ self.DDM.Length2 = self.DDM.Length2 + param.Length
+
+ return true
+ end,
+
+ --- Gets a parameter from the DRDA parameter table
+ --
+ -- @param codepoint number containing the parameter type ro retrieve
+ -- @return param DRDAParameter containing the requested parameter
+ getParameter = function( self, codepoint )
+ for _, v in ipairs( self.Parameters ) do
+ if ( v.CodePoint == codepoint ) then
+ return v
+ end
+ end
+ return
+ end,
+
+ --- Converts the DRDA class to a string
+ --
+ -- @return data containing the object instance
+ toString = function(self)
+ local data
+
+ if ( not(self.DDM) ) then
+ stdnse.print_debug("db2.DRDA.toString: DDM cannot be nil")
+ return nil
+ end
+
+ data = bin.pack(">SCCSSS", self.DDM.Length, self.DDM.Magic, self.DDM.Format, self.DDM.CorelId, self.DDM.Length2, self.DDM.CodePoint )
+ for k,v in ipairs(self.Parameters) do
+ data = data .. v:toString()
+ end
+ return data
+ end,
+
+ --- Sends the DRDA over the db2socket
+ --
+ -- @param socket DB2Socket over which to send the data
+ -- @return Status (true or false).
+ -- @return Error code (if status is false).
+ send = function( self, db2socket )
+ return db2socket:send( self:toString() )
+ end,
+
+ --- Receives data from the db2socket and builds a DRDA object
+ --
+ -- @param db2socket from which to read data
+ -- @return Status (true or false).
+ -- @return Data (if status is true) or error string (if status is false).
+ receive = function( self, db2socket )
+ local DDM_SIZE = 10
+ local status, data, ddm, param
+ local pos = 1
+
+ -- first read atleast enough so that we can populate the DDM
+ status, data = db2socket:recv( DDM_SIZE )
+ if ( not(status) ) then
+ stdnse.print_debug("db2.DRDA.receive: %s", data)
+ return false, ("Failed to read at least %d bytes from socket"):format(DDM_SIZE)
+ end
+
+ ddm = DDM:new()
+ ddm:fromString( data )
+ self:setDDM( ddm )
+
+ status, data = db2socket:recv( ddm.Length - 10 )
+ if ( not(status) ) then
+ return false, ("Failed to read the remaining %d bytes of the DRDA message")
+ end
+
+ -- add parameters until pos reaches the "end"
+ repeat
+ param = DRDAParameter:new()
+ pos = param:fromString( data, pos )
+ self:addParameter( param )
+ until ( #data <= pos )
+
+ return true
+ end,
+
+}
+
+-- The DRDAParameter class implements the DRDA parameters
+DRDAParameter = {
+
+ --- DRDA Parameter constructor
+ --
+ -- @param codepoint number containing the codepoint value
+ -- @param data string containing the data portion of the DRDA parameter
+ -- @return o DRDAParameter object
+ new = function(self, codepoint, data)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.CodePoint = codepoint
+ if ( data ) then
+ o.Data = data
+ o.Length = #o.Data + 4
+ else
+ o.Length = 4
+ end
+ return o
+ end,
+
+ --- Converts the DRDA Parameter object to a string
+ --
+ -- @return data string containing the DRDA Parameter
+ toString = function( self )
+ local data = bin.pack(">SS", self.Length, self.CodePoint )
+
+ if ( self.Data ) then
+ data = data .. bin.pack("A", self.Data)
+ end
+ return data
+ end,
+
+ --- Builds a DRDA Parameter from a string
+ --
+ -- @param data string from which to build the DRDA Parameter
+ -- @param pos number containing the offset into data
+ -- @return pos the new position after processing, -1 on error
+ fromString = function( self, data, pos )
+ if( #data < 4 ) then
+ return -1
+ end
+ pos, self.Length, self.CodePoint = bin.unpack( ">SS", data, pos )
+
+ -- make sure the Length is assigned a value even though 0(nil) is returned
+ self.Length = self.Length or 0
+
+ if ( self.Length > 0 ) then
+ pos, self.Data = bin.unpack("A" .. self.Length - 4, data, pos )
+ end
+ return pos
+ end,
+
+ --- Returns the data portion of the parameter as an ASCII string
+ --
+ -- @return str containing the data portion of the DRDA parameter as ASCII
+ getDataAsASCII = function( self )
+ return StringUtil.toASCII( self.Data )
+ end,
+
+ --- Returns the data in EBCDIC format
+ --
+ -- @return str containing the data portion of the DRDA parameter in EBCDIC
+ getData = function( self )
+ return self.Data
+ end,
+
+}
+
+-- Distributed data management (DDM)
+DDM = {
+
+ Formats =
+ {
+ RESERVED = 0x80,
+ CHAINED = 0x40,
+ CONTINUE = 0x20,
+ SAME_CORRELATION = 0x10,
+ },
+
+ Length = 10,
+ Magic = 0xD0,
+ Format = 0x41,
+ CorelId = 1,
+ Length2 = 4,
+ CodePoint = 0,
+
+ --- Creates a new DDM packet
+ --
+ -- @param codepoint
+ -- @param format
+ -- @param corelid
+ -- @return DDM object
+ new = function(self, codepoint, format, corelid)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.CodePoint = codepoint
+ if ( format ) then
+ o.Format = format
+ end
+ if ( corelid ) then
+ o.CorelId = corelid
+ end
+ return o
+ end,
+
+ --- Converts the DDM object to a string
+ toString = function( self )
+ return bin.pack(">SCCSSS", self.Length, self.Magic, self.Format, self.CorelId, self.Length2, self.CodePoint)
+ end,
+
+ --- Constructs a DDM object from a string
+ --
+ -- @param str containing the data from which to construct the object
+ fromString = function( self, str )
+ local DDM_SIZE = 10
+ local pos = 1
+
+ if ( #str < DDM_SIZE ) then
+ return -1, ("db2.DDM.fromString: str was less than DDM_SIZE (%d)"):format( DDM_SIZE )
+ end
+
+ pos, self.Length, self.Magic, self.Format, self.CorelId, self.Length2, self.CodePoint = bin.unpack( ">SCCSSS", str )
+ return pos
+ end,
+
+ --- Verifiers if there are additional DRDA's following
+ --
+ -- @return true if the DRDA is to be chained, false if it's the last one
+ isChained = function( self )
+ if ( bit.band( self.Format, DDM.Formats.CHAINED ) == DDM.Formats.CHAINED ) then
+ return true
+ end
+ return false
+ end,
+
+ --- Set the DRDA as chained (more following)
+ --
+ -- @param chained boolean true if more DRDA's are following
+ setChained = function( self, chained )
+ if ( self:isChained() ) then
+ self.Format = bit.bxor( self.Format, self.Formats.CHAINED )
+ else
+ self.Format = bit.bor( self.Format, self.Formats.CHAINED )
+ end
+ end,
+
+}
+
+-- static DRDA packet construction class
+Command =
+{
+ --- Builds an EXCSAT DRDA packet
+ --
+ -- @param extname string containing the external name
+ -- @param srvname string containing the server name
+ -- @param rellev string containing the server product release level
+ -- @param mgrlvlls string containing the manager level list
+ -- @param srvclass string containing the server class name
+ -- @return drda DRDA instance
+ EXCSAT = function( extname, srvname, rellev, mgrlvlls, srvclass )
+ local drda = DRDA:new( DDM:new( CodePoint.EXCSAT ) )
+
+ drda:addParameter( DRDAParameter:new( CodePoint.EXTNAM, StringUtil.toEBCDIC( extname ) ) )
+ drda:addParameter( DRDAParameter:new( CodePoint.SRVNAM, StringUtil.toEBCDIC( srvname ) ) )
+ drda:addParameter( DRDAParameter:new( CodePoint.SRVRLSLV, StringUtil.toEBCDIC( rellev ) ) )
+ drda:addParameter( DRDAParameter:new( CodePoint.MGRLVLLS, mgrlvlls ) )
+ drda:addParameter( DRDAParameter:new( CodePoint.SRVCLSNM, StringUtil.toEBCDIC( srvclass ) ) )
+
+ return drda
+ end,
+
+ --- Builds an ACCSEC DRDA packet
+ --
+ -- @param secmec number containing the security mechanism ID
+ -- @param database string containing the database name
+ -- @return drda DRDA instance
+ ACCSEC = function( secmec, database )
+ local drda = DRDA:new( DDM:new( CodePoint.ACCSEC ) )
+ drda:addParameter( DRDAParameter:new( CodePoint.SECMEC, secmec ))
+ drda:addParameter( DRDAParameter:new( CodePoint.RDBNAM, StringUtil.toEBCDIC(StringUtil.padWithChar(database,' ', 18)) ))
+
+ return drda
+ end,
+
+ --- Builds a SECCHK DRDA packet
+ --
+ -- @param secmec number containing the security mechanism ID
+ -- @param database string containing the database name
+ -- @param username string
+ -- @param password string
+ -- @return drda DRDA instance
+ SECCHK = function( secmec, database, username, password )
+ local drda = DRDA:new( DDM:new( CodePoint.SECCHK ) )
+ drda:addParameter( DRDAParameter:new( CodePoint.SECMEC, secmec ))
+ drda:addParameter( DRDAParameter:new( CodePoint.RDBNAM, StringUtil.toEBCDIC(StringUtil.padWithChar(database,' ', 18)) ))
+ drda:addParameter( DRDAParameter:new( CodePoint.USRID, StringUtil.toEBCDIC(username) ) )
+ drda:addParameter( DRDAParameter:new( CodePoint.PASSWORD, StringUtil.toEBCDIC(password) ) )
+
+ return drda
+ end,
+
+ --- Builds an ACCRDB DRDA packet
+ --
+ -- @param database string containing the database name
+ -- @param rdbaccl string containing the RDB access manager class
+ -- @param prdid string containing the product id
+ -- @param typdefnam string containing the data type definition name
+ -- @param typdefovr string containing the data type definition override
+ -- @return drda DRDA instance
+ ACCRDB = function( database, rdbaccl, prdid, prddata, typdefnam, typdefovr )
+ local drda = DRDA:new( DDM:new( CodePoint.ACCRDB ) )
+ drda:addParameter( DRDAParameter:new( CodePoint.RDBNAM, StringUtil.toEBCDIC(StringUtil.padWithChar(database,' ', 18)) ) )
+
+ if ( rdbaccl ) then
+ drda:addParameter( DRDAParameter:new( CodePoint.RDBACCL, rdbaccl ) )
+ end
+ if ( prdid ) then
+ drda:addParameter( DRDAParameter:new( CodePoint.PRDID, StringUtil.toEBCDIC( prdid ) ) )
+ end
+ if ( prddata ) then
+ drda:addParameter( DRDAParameter:new( CodePoint.PRDDATA, StringUtil.toEBCDIC( prddata ) ) )
+ end
+ if( typdefnam ) then
+ drda:addParameter( DRDAParameter:new( CodePoint.TYPDEFNAM, StringUtil.toEBCDIC( typdefnam ) ) )
+ end
+ if( typdefovr ) then
+ drda:addParameter( DRDAParameter:new( CodePoint.TYPDEFOVR, typdefovr ) )
+ end
+
+ return drda
+ end
+
+}
+
+
+-- Helper Class
+Helper = {
+
+ new = function(self)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ --- Connect to the DB2 host
+ --
+ -- @param host table
+ -- @param port table
+ -- @return Status (true or false).
+ -- @return Error code (if status is false).
+ connect = function( self, host, port )
+ self.db2socket = DB2Socket:new()
+ return self.db2socket:connect(host.ip, port.number, port.protocol)
+ end,
+
+ --- Closes an open connection.
+ --
+ -- @return Status (true or false).
+ -- @return Error code (if status is false).
+ close = function( self )
+ self.db2socket:close()
+ end,
+
+ --- Returns Server Information (name, platform, version)
+ --
+ -- @return table containing extname, srvclass,
+ -- srvname and prodrel
+ getServerInfo = function( self )
+ local mgrlvlls = bin.pack("H", "1403000724070008240f00081440000814740008")
+ local drda_excsat = Command.EXCSAT( "", "", "", mgrlvlls, "" )
+ local drda, response, param, status, err
+
+ status, err = self.db2socket:sendDRDA( { drda_excsat } )
+ if ( not(status) ) then
+ return false, err
+ end
+
+ status, drda = self.db2socket:recvDRDA()
+ if( not(status) ) then
+ return false, drda
+ end
+
+ if ( #drda > 0 and drda[1].DDM.CodePoint == CodePoint.EXCSATRD ) then
+ response = {}
+ param = drda[1]:getParameter( CodePoint.EXTNAM )
+ if ( param ) then
+ response.extname = param:getDataAsASCII()
+ end
+ param = drda[1]:getParameter( CodePoint.SRVCLSNM )
+ if ( param ) then
+ response.srvclass = param:getDataAsASCII()
+ end
+ param = drda[1]:getParameter( CodePoint.SRVNAM )
+ if ( param ) then
+ response.srvname = param:getDataAsASCII()
+ end
+ param = drda[1]:getParameter( CodePoint.SRVRLSLV )
+ if ( param ) then
+ response.prodrel = param:getDataAsASCII()
+ end
+ else
+ return false, "The response contained no EXCSATRD"
+ end
+
+ return true, response
+ end,
+
+ --- Login to DB2 database server
+ --
+ -- @param database containing the name of the database
+ -- @param username containing the authentication username
+ -- @param password containing the authentication password
+ -- @return Status (true or false)
+ -- @return err message (if status if false)
+ login = function( self, database, username, password )
+ local drda = {}
+ local data, param, status, err, _
+
+ local mgrlvlls = bin.pack("H", "1403000724070008240f00081440000814740008")
+ local secmec, prdid = "\00\03", "JCC03010"
+
+ local drda_excsat = Command.EXCSAT( "", "", "", mgrlvlls, "" )
+ local drda_accsec = Command.ACCSEC( secmec, database )
+ local drda_secchk = Command.SECCHK( secmec, database, username, password )
+ local drda_accrdb = Command.ACCRDB( database )
+
+ status, err = self.db2socket:sendDRDA( { drda_excsat, drda_accsec } )
+ if ( not(status) ) then
+ stdnse.print_debug("db2.Helper.login: ERROR: DB2Socket error: %s", err )
+ return false, ("ERROR: DB2Socket error: %s"):format( err )
+ end
+
+ status, drda = self.db2socket:recvDRDA()
+ if( not(status) ) then
+ stdnse.print_debug("db2.Helper.login: ERROR: DB2Socket error: %s", drda )
+ return false, ("ERROR: DB2Socket error: %s"):format( drda )
+ end
+
+ if ( 2 > #drda ) then
+ stdnse.print_debug("db2.Helper.login: db2.Helper.login: ERROR: Expected two DRDA records")
+ return false, "ERROR: Expected two DRDA records"
+ end
+
+ -- Check if the DB is accessible
+ for i=1, #drda do
+ if ( drda[i].DDM.CodePoint == CodePoint.RDBNFNRM or
+ drda[i].DDM.CodePoint == CodePoint.RDBAFLRM ) then
+ stdnse.print_debug("db2.Helper.login: ERROR: RDB not found")
+ return false, "ERROR: Database not found"
+ end
+ end
+
+ param = drda[2]:getParameter( CodePoint.SECMEC )
+ if ( not(param) ) then
+ stdnse.print_debug("db2.Helper.login: ERROR: Response did not contain any valid security mechanisms")
+ return false, "ERROR: Response did not contain any valid security mechanisms"
+ end
+
+ if ( select(2, bin.unpack(">S", param:getData())) ~= SecMec.USER_PASSWORD ) then
+ stdnse.print_debug("db2.Helper.login: ERROR: Securite Mechanism not supported")
+ return false, "ERROR: Security mechanism not supported"
+ end
+
+ status, err = self.db2socket:sendDRDA( { drda_secchk, drda_accrdb } )
+ if ( not(status) ) then
+ stdnse.print_debug("db2.Helper.login: ERROR: DB2Socket error: %s", err )
+ return false, ("ERROR: DB2Socket error: %s"):format( err )
+ end
+
+ status, drda = self.db2socket:recvDRDA()
+ if( not(status) ) then
+ stdnse.print_debug("db2.Helper.login: ERROR: DB2Socket error: %s", drda )
+ return false, ("ERROR: DB2Socket error: %s"):format( drda )
+ end
+
+ param = drda[1]:getParameter( CodePoint.SECCHKCD )
+ if ( not(param) ) then
+ stdnse.print_debug("db2.Helper.login: ERROR: Authentication failed")
+ return false, "ERROR: Authentication failed"
+ end
+
+ local secchkcd = select( 2, bin.unpack( "C", param:getData() ) )
+ if ( 0 ~= secchkcd ) then
+ stdnse.print_debug( "db2.Helper.login: ERROR: Authentication failed, error code: %d", secchkcd )
+ return false, ("ERROR: Authentication failed, error code: %d"):format(secchkcd)
+ end
+
+ return true
+ end,
+
+}
+
+-- The DB2Socket class
+--
+-- Allows for reading an exact count of bytes opposed to the nmap socket
+-- implementation that does at least count of bytes.
+--
+-- The DB2Socket makes use of nmaps underlying socket implementation and
+-- buffers the bytes exceeding the number asked for. The next call to the
+-- recv function will fetch bytes from the buffer and call
+-- the recieve_bytes function of the underlying when there
+-- are no more buffered bytes.
+--
+-- The connect, close and send
+-- functions are wrappers around the same functions of the nmap socket code.
+-- Consult the nsedoc for additional information on these.
+DB2Socket = {
+
+ retries = 3,
+
+ new = function(self)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.Socket = nmap.new_socket()
+ o.Buffer = nil
+ return o
+ end,
+
+
+ --- Establishes a connection.
+ --
+ -- @param hostid Hostname or IP address.
+ -- @param port Port number.
+ -- @param protocol "tcp", "udp", or
+ -- @return Status (true or false).
+ -- @return Error code (if status is false).
+ connect = function( self, hostid, port, protocol )
+ return self.Socket:connect( hostid, port, protocol )
+ end,
+
+ --- Closes an open connection.
+ --
+ -- @return Status (true or false).
+ -- @return Error code (if status is false).
+ close = function( self )
+ return self.Socket:close()
+ end,
+
+ --- Opposed to the socket:receive_bytes function, that returns
+ -- at least x bytes, this function returns the amount of bytes requested.
+ --
+ -- @param count of bytes to read
+ -- @return true on success, false on failure
+ -- @return data containing bytes read from the socket
+ -- err containing error message if status is false
+ recv = function( self, count )
+ local status, data
+
+ self.Buffer = self.Buffer or ""
+
+ if ( #self.Buffer < count ) then
+ status, data = self.Socket:receive_bytes( count - #self.Buffer )
+ if ( not(status) ) then
+ return false, data
+ end
+ self.Buffer = self.Buffer .. data
+ end
+
+ data = self.Buffer:sub( 1, count )
+ self.Buffer = self.Buffer:sub( count + 1)
+
+ return true, data
+ end,
+
+ --- Sends data over the socket
+ --
+ -- @return Status (true or false).
+ -- @return Error code (if status is false).
+ send = function( self, data )
+ return self.Socket:send( data )
+ end,
+
+ --- Sends a single or multiple DRDA's over the socket
+ --
+ -- @param drda a single or a table containing multiple DRDA's
+ -- @return Status (true or false).
+ -- @return Error code (if status is false).
+ sendDRDA = function( self, drda )
+ local data = ""
+
+ if ( 0 == #drda ) then
+ data = drda:toString()
+ else
+ -- do some DDM fixup in here
+ for i=1, #drda do
+ if ( i == 1 and #drda > 1 ) then
+ drda[1].DDM.Format = 0x41
+ else
+ drda[i].DDM.Format = 0x01
+ end
+ drda[i].DDM.CorelId = i
+ data = data .. drda[i]:toString()
+ end
+ end
+
+ return self:send(data)
+ end,
+
+ --- Reads a single or multiple DRDA's of the socket
+ --
+ -- @return status (true or false)
+ -- @return drda table containing retrieved DRDA's
+ recvDRDA = function( self )
+ local status, err
+ local drda_tbl = {}
+
+ repeat
+ local drda = DRDA:new()
+ status, err = drda:receive( self )
+ if ( not(status) ) then
+ return false, err
+ end
+ table.insert(drda_tbl, drda)
+ until ( not(drda.DDM:isChained()) )
+
+ return true, drda_tbl
+ end,
+}
+
+-- EBCDIC/ASCII Conversion tables
+a2e_hex = "00010203372D2E2F1605250B0C0D0E0F101112133C3D322618193F271C1D1E1F"
+a2e_hex = a2e_hex .. "405A7F7B5B6C507D4D5D5C4E6B604B61F0F1F2F3F4F5F6F7F8F97A5E4C7E6E6F"
+a2e_hex = a2e_hex .. "7CC1C2C3C4C5C6C7C8C9D1D2D3D4D5D6D7D8D9E2E3E4E5E6E7E8E9ADE0BD5F6D"
+a2e_hex = a2e_hex .. "79818283848586878889919293949596979899A2A3A4A5A6A7A8A9C04FD0A107"
+a2e_hex = a2e_hex .. "202122232415061728292A2B2C090A1B30311A333435360838393A3B04143EE1"
+a2e_hex = a2e_hex .. "4142434445464748495152535455565758596263646566676869707172737475"
+a2e_hex = a2e_hex .. "767778808A8B8C8D8E8F909A9B9C9D9E9FA0AAABAC4AAEAFB0B1B2B3B4B5B6B7"
+a2e_hex = a2e_hex .. "B8B9BABBBC6ABEBFCACBCCCDCECFDADBDCDDDEDFEAEBECEDEEEFFAFBFCFDFEFF"
+
+e2a_hex = "000102039C09867F978D8E0B0C0D0E0F101112139D8508871819928F1C1D1E1F"
+e2a_hex = e2a_hex .. "80818283840A171B88898A8B8C050607909116939495960498999A9B14159E1A"
+e2a_hex = e2a_hex .. "20A0A1A2A3A4A5A6A7A8D52E3C282B7C26A9AAABACADAEAFB0B121242A293B5E"
+e2a_hex = e2a_hex .. "2D2FB2B3B4B5B6B7B8B9E52C255F3E3FBABBBCBDBEBFC0C1C2603A2340273D22"
+e2a_hex = e2a_hex .. "C3616263646566676869C4C5C6C7C8C9CA6A6B6C6D6E6F707172CBCCCDCECFD0"
+e2a_hex = e2a_hex .. "D17E737475767778797AD2D3D45BD6D7D8D9DADBDCDDDEDFE0E1E2E3E45DE6E7"
+e2a_hex = e2a_hex .. "7B414243444546474849E8E9EAEBECED7D4A4B4C4D4E4F505152EEEFF0F1F2F3"
+e2a_hex = e2a_hex .. "5C9F535455565758595AF4F5F6F7F8F930313233343536373839FAFBFCFDFEFF"
+
+-- Creates the lookup tables needed for conversion
+a2e_tbl = bin.pack("H", a2e_hex)
+e2a_tbl = bin.pack("H", e2a_hex)
+
+-- Handle EBCDIC/ASCII conversion
+StringUtil =
+{
+ --- Converts an ASCII string to EBCDIC
+ --
+ -- @param ascii string containing the ASCII value
+ -- @return string containing the EBCDIC value
+ toEBCDIC = function( ascii )
+ local val, ret = 0, ""
+
+ for i=1, #ascii do
+ val = ascii.byte(ascii,i) + 1
+ ret = ret .. a2e_tbl:sub(val, val)
+ end
+ return ret
+ end,
+
+ --- Converts an EBCDIC string to ASCII
+ --
+ -- @param ebcdic string containing EBCDIC value
+ -- @return string containing ASCII value
+ toASCII = function( ebcdic )
+ local val, ret = 0, ""
+
+ for i=1, #ebcdic do
+ val = ebcdic.byte(ebcdic,i) + 1
+ ret = ret .. e2a_tbl:sub(val, val)
+ end
+ return ret
+ end,
+
+ --- Pads a string with a character
+ --
+ -- @param str string to pad
+ -- @param chr char to pad with
+ -- @len the total length of the finnished string
+ -- @return str string containing the padded string
+ padWithChar = function( str, chr, len )
+ if ( len < #str ) then
+ return str
+ end
+ for i=1, (len - #str) do
+ str = str .. chr
+ end
+ return str
+ end,
+}
diff --git a/scripts/db2-brute.nse b/scripts/db2-brute.nse
new file mode 100644
index 000000000..5d4a60ee6
--- /dev/null
+++ b/scripts/db2-brute.nse
@@ -0,0 +1,166 @@
+description = [[
+Performs password guessing against IBM DB2
+]]
+
+---
+-- @usage
+-- nmap -p 50000 --script db2-brute
+--
+-- @output
+-- 50000/tcp open ibm-db2
+-- | db2-brute:
+-- |_ db2admin:db2admin => Login Correct
+--
+--
+-- @args db2-brute.threads the amount of accounts to attempt to brute force in parallell (default 10)
+-- @args db2-brute.dbname the database name against which to guess passwords (default SAMPLE)
+--
+
+author = "Patrik Karlsson"
+license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
+categories={"intrusive", "auth"}
+
+require "stdnse"
+require "shortport"
+require "db2"
+require "unpwdb"
+
+-- Version 0.3
+-- Created 05/08/2010 - v0.1 - created by Patrik Karlsson
+-- Revised 05/09/2010 - v0.2 - re-wrote as multi-threaded
+-- Revised 05/10/2010 - v0.3 - revised parallellised design
+
+portrule = shortport.port_or_service({50000,60000},"ibm-db2", "tcp", {"open", "open|filtered"})
+
+--- Credential iterator
+--
+-- @param usernames iterator from unpwdb
+-- @param passwords iterator from unpwdb
+-- @return username string
+-- @return password string
+local function new_usrpwd_iterator (usernames, passwords)
+ local function next_username_password ()
+ for username in usernames do
+ for password in passwords do
+ coroutine.yield(username, password)
+ end
+ passwords("reset")
+ end
+ while true do coroutine.yield(nil, nil) end
+ end
+ return coroutine.wrap(next_username_password)
+end
+
+--- Iterates over the password list and guesses passwords
+--
+-- @param host table with information as recieved by action
+-- @param port table with information as recieved by action
+-- @param database string containing the database name
+-- @param username string containing the username against which to guess
+-- @param valid_accounts table in which to store found accounts
+doLogin = function( host, port, database, creds, valid_accounts )
+ local helper, status, response, passwords
+ local condvar = nmap.condvar( valid_accounts )
+
+ for username, password in creds do
+ -- Checks if a password was already discovered for this account
+ if ( nmap.registry.db2users == nil or nmap.registry.db2users[username] == nil ) then
+ helper = db2.Helper:new()
+ helper:connect( host, port )
+ stdnse.print_debug( "Trying %s/%s against %s...", username, password, host.ip )
+ status, response = helper:login( database, username, password )
+ helper:close()
+
+ if ( status ) then
+ -- Add credentials for future db2 scripts to use
+ if nmap.registry.db2users == nil then
+ nmap.registry.db2users = {}
+ end
+ nmap.registry.db2users[username]=password
+ table.insert( valid_accounts, string.format("%s:%s => Login Correct", username, password:len()>0 and password or "" ) )
+ end
+ end
+ end
+ condvar("broadcast")
+end
+
+--- Checks if the supplied database exists
+--
+-- @param host table with information as recieved by action
+-- @param port table with information as recieved by action
+-- @param database string containing the database name
+-- @return status true on success, false on failure
+isValidDb = function( host, port, database )
+ local status, response
+ local helper = db2.Helper:new()
+
+ helper:connect( host, port )
+ -- Authenticate with a static probe account to see if the db is valid
+ status, response = helper:login( database, "dbnameprobe1234", "dbnameprobe1234" )
+ helper:close()
+
+ if ( not(status) and response:match("Database not found") ) then
+ return false
+ end
+ return true
+end
+
+--- Returns the amount of currenlty active threads
+--
+-- @param threads table containing the list of threads
+-- @return count number containing the number of non-dead threads
+threadCount = function( threads )
+ local count = 0
+
+ for thread in pairs(threads) do
+ if ( coroutine.status(thread) == "dead" ) then
+ threads[thread] = nil
+ else
+ count = count + 1
+ end
+ end
+ return count
+end
+
+action = function( host, port )
+
+ local result, response, status = {}, nil, nil
+ local valid_accounts, threads = {}, {}
+ local usernames, passwords, creds
+ local database = nmap.registry.args['db2-brute.dbname'] or "SAMPLE"
+ local condvar = nmap.condvar( valid_accounts )
+ local max_threads = nmap.registry.args['db2-brute.threads'] and tonumber( nmap.registry.args['db2-brute.threads'] ) or 10
+
+ -- Check if the DB specified is valid
+ if( not(isValidDb(host, port, database)) ) then
+ return ("The databases %s was not found. (Use --script-args db2-brute.dbname= to specify database)"):format(database)
+ end
+
+ status, usernames = unpwdb.usernames()
+ if ( not(status) ) then
+ return "Failed to load usernames"
+ end
+
+ -- make sure we have a valid pw file
+ status, passwords = unpwdb.passwords()
+ if ( not(status) ) then
+ return "Failed to load passwords"
+ end
+
+ creds = new_usrpwd_iterator( usernames, passwords )
+
+ stdnse.print_debug("Starting brute force with %d threads", max_threads )
+
+ for i=1,max_threads do
+ local co = stdnse.new_thread( doLogin, host, port, database, creds, valid_accounts )
+ threads[co] = true
+ end
+
+ -- wait for all threads to finnish running
+ while threadCount(threads)>0 do
+ condvar("wait")
+ end
+
+ return stdnse.format_output(true, valid_accounts)
+
+end
diff --git a/scripts/db2-info.nse b/scripts/db2-info.nse
index c732854ef..b3d9ff4fd 100644
--- a/scripts/db2-info.nse
+++ b/scripts/db2-info.nse
@@ -1,356 +1,89 @@
-description = [[
-Attempts to extract information from IBM DB2 Server instances. The script sends a
-DB2 EXCSAT (exchange server attributes) command packet and parses the response.
-]]
-
--- rev 1.3 (2009-12-16)
-
----
--- @output
--- PORT STATE SERVICE
--- 50000/tcp open ibm-db2
--- | db2-info: DB2 Version: 8.02.9
--- | Server Platform: QDB2/SUN
--- | Instance Name: db2inst1
--- |_ External Name: db2inst1db2agent00002B430
-
-author = "Tom Sellers"
-
-license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
-
-categories = {"safe", "discovery", "version"}
-
-require "stdnse"
-require "shortport"
-
-portrule = shortport.port_or_service({50000,60000},"ibm-db2", "tcp", {"open", "open|filtered"})
-
--- This function processes a section of the EXCSAT response packet
-
---@param response This is the data returned from the server as a result of the client query.
---@param position This is the position within the response that this function will start processing from.
---@param ebcdic2ascii This is a table containing a conversion chart for returning the ASCII value of EBCDIC encoded HEX value.
---@return section_length This is the length of the currect section. Will be used to move position for processing next section.
---@return data_string This string contains the data pulled from this section of the server response.
-local function process_block(response, position, ebcdic2ascii)
-
- -- This fuction assumes that the current position is the start of a section within
- -- the DB2 EXCSAT response packet
-
- -- Get the length of this section of the response packet
- local section_length = string.format("%d",string.byte(response,position +1)) .. string.format("%d",string.byte(response,position + 2))
- position = position + 2
-
- -- locate the data string and convert it from EBCDIC to ASCII
- local i = 0
- local data_string = ""
- for i = (position + 3),(position + section_length -2 ),1 do
- -- stdnse.print_debug("%s","INFO: Current postion (i) = " .. i)
- -- stdnse.print_debug("%s","INFO: Hex value = " .. string.format("%x",string.byte(response,i)))
- -- stdnse.print_debug("%s","INFO: Current data_string = " .. data_string)
- if string.format("%x",string.byte(response,i)) == "0" then
- break
- end
- data_string = data_string .. ebcdic2ascii[string.format("%x",string.byte(response,i))]
- end
-
- return section_length, data_string
-
-end -- fuction process_block
-
-
-
-action = function(host, port)
-
- local ebcdic2ascii = {
- -- The following reference was used for this table: http://www.simotime.com/asc2ebc1.htm
- ["00"] = string.format("%c", 00),
- ["40"] = " ",
- ["81"] = "a",
- ["82"] = "b",
- ["83"] = "c",
- ["84"] = "d",
- ["85"] = "e",
- ["86"] = "f",
- ["87"] = "g",
- ["88"] = "h",
- ["89"] = "i",
- ["91"] = "j",
- ["92"] = "k",
- ["93"] = "l",
- ["94"] = "m",
- ["95"] = "n",
- ["96"] = "o",
- ["97"] = "p",
- ["98"] = "q",
- ["99"] = "r",
- ["a2"] = "s",
- ["a3"] = "t",
- ["a4"] = "u",
- ["a5"] = "v",
- ["a6"] = "w",
- ["a7"] = "x",
- ["a8"] = "y",
- ["a9"] = "z",
- ["c1"] = "A",
- ["c2"] = "B",
- ["c3"] = "C",
- ["c4"] = "D",
- ["c5"] = "E",
- ["c6"] = "F",
- ["c7"] = "G",
- ["c8"] = "H",
- ["c9"] = "I",
- ["d1"] = "J",
- ["d2"] = "K",
- ["d3"] = "L",
- ["d4"] = "M",
- ["d5"] = "N",
- ["d6"] = "O",
- ["d7"] = "P",
- ["d8"] = "Q",
- ["d9"] = "R",
- ["e2"] = "S",
- ["e3"] = "T",
- ["e4"] = "U",
- ["e5"] = "V",
- ["e6"] = "W",
- ["e7"] = "X",
- ["e8"] = "Y",
- ["e9"] = "Z",
- ["f0"] = 0,
- ["f1"] = 1,
- ["f2"] = 2,
- ["f3"] = 3,
- ["f4"] = 4,
- ["f5"] = 5,
- ["f6"] = 6,
- ["f7"] = 7,
- ["f8"] = 8,
- ["f9"] = 9,
- ["4b"] = ".",
- ["4c"] = "<",
- ["4d"] = "(",
- ["4e"] = "+",
- ["4f"] = "|",
- ["5a"] = "!",
- ["5b"] = "$",
- ["5c"] = "*",
- ["5d"] = ")",
- ["5e"] = ";",
- ["60"] = "-",
- ["61"] = "/",
- ["6b"] = ",",
- ["6c"] = "%",
- ["6d"] = "_",
- ["6e"] = ">",
- ["6f"] = "?",
- ["79"] = "`",
- ["7a"] = ":",
- ["7b"] = "#",
- ["7c"] = "@",
- ["7d"] = "'",
- ["7e"] = "=",
- ["7f"] = "\"",
- ["a1"] = "~",
- ["ba"] = "[",
- ["bb"] = "]",
- ["c0"] = "{",
- ["d0"] = "}",
- ["e0"] = "\\" -- escape the \ character
- }
-
- -- ebcdic2ascii does not contain all value, set a default value
- -- to improve stability.
- setmetatable(ebcdic2ascii, { __index = function() return " " end })
-
- -- 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.print_debug("%s", "db2-info: ERROR communicating with " .. host.ip .. " on port " .. port.number .. "/" .. port.protocol)
- socket:close()
- end
-
- local try = nmap.new_try(catch)
-
- try(socket:connect(host.ip, port.number, "tcp"))
-
- -- Build DB2 EXCSAT (exchange server attributes) command packet
-
- local query = string.char(0x00, 0x98, 0xd0, 0x41, 0x00, 0x01, 0x00, 0x92, 0x10, 0x41) -- Header
-
- -- NOTE: The server's response packet is in the same format at the client query packet being built
- -- in the section below.
-
- -- External Name section: first two bytes (00,48) are section length in HEX, next bytes (11,5e) are section identifier for External Name
- -- In this packet the external name is 'db2jcc_application JCC03570300' encoded in EBCDIC
- query = query .. string.char(0x00, 0x48, 0x11, 0x5e, 0x84, 0x82, 0xf2, 0x91, 0x83, 0x83, 0x6d, 0x81, 0x97, 0x97, 0x93, 0x89)
- query = query .. string.char(0x83, 0x81, 0xa3, 0x89, 0x96, 0x95, 0x40, 0x40, 0xd1, 0xc3, 0xc3, 0xf0, 0xf3, 0xf5, 0xf7, 0xf0)
- query = query .. string.char(0xf3, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
- query = query .. string.char(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
- query = query .. string.char(0x00, 0x00, 0x00, 0x60, 0xf0, 0xf0, 0xf0, 0xf1)
-
- -- Client Name section: first two bytes (00,16) are section length in HEX, next two bytes (11,6d) are section identifier for Server Name
- -- In the request packet Server Name = client name. The value here is all spaces, encoded in EBCDIC
- query = query .. string.char(0x00, 0x16, 0x11, 0x6d, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40)
- query = query .. string.char(0x40, 0x40, 0x40, 0x40, 0x40, 0x40)
-
- -- Product Release Level section: This section is not as important in the client query as it is in the server response.
- -- The first two bytes (00,0c) are section length in HEX, next two bytes (11,5a) are section identifier for Product Release Level
- -- The value here is 'JCC03570' encoded in EBCDIC
- query = query .. string.char(0x00, 0x0c, 0x11, 0x5a, 0xd1, 0xc3, 0xc3, 0xf0, 0xf3, 0xf5, 0xf7, 0xf0)
-
- -- Manager level section: first two bytes (00,18) are section length in HEX, next two bytes (14,04) are section identifier for Manager-Level List
- query = query .. string.char(0x00, 0x18, 0x14, 0x04, 0x14, 0x03, 0x00, 0x07, 0x24, 0x07, 0x00, 0x0a, 0x24, 0x0f, 0x00, 0x08)
- query = query .. string.char(0x14, 0x40, 0x00, 0x09, 0x14, 0x74, 0x00, 0x08)
-
- -- Server Class section: first two bytes (00,0c) are section length in HEX, next two bytes (11,47) are section identifier for Server Class Name
- -- This section is essentially platform software information. The value here is 'QDB2/JBM' encoded in EBCDIC)
- query = query .. string.char(0x00, 0x0c, 0x11, 0x47, 0xd8, 0xc4, 0xc2, 0xf2, 0x61, 0xd1, 0xe5, 0xd4)
-
- -- Access Security section
- query = query .. string.char(0x00, 0x26, 0xd0, 0x01, 0x00, 0x02, 0x00, 0x20, 0x10, 0x6d, 0x00, 0x06, 0x11, 0xa2, 0x00, 0x03)
-
-
- -- Database name section: This is the client's query for a specific database. A DB2 default database name, 'db2insta1', was chosen.
- -- It is encoded below in EBCDIC. The first two bytes (00,16) are section length in HEX, next two bytes (21,10) are section identifier
- -- for Relational Database Name
- query = query .. string.char(0x00, 0x16, 0x21, 0x10, 0x84, 0x82, 0xf2, 0x89, 0x95, 0xa2, 0xa3, 0xf1, 0x40, 0x40, 0x40, 0x40)
- query = query .. string.char(0x40, 0x40, 0x40, 0x40, 0x40, 0x40)
-
- try(socket:send(query))
-
- local status
- local response
-
- -- read in any response we might get
- status, response = socket:receive()
-
- socket:close()
-
- if (not status) or (response == "TIMEOUT") or (response == nil) then
- stdnse.print_debug("%s","db2-info: ERROR: No data, ending communications with " .. host.ip .. ":" .. port.number .. "/" .. port.protocol)
- return
- end
-
- local position = 0
-
- -- Check to see if the data is actually a DB2 DDM EXCSAT response.
- -- 0d in the 3rd byte of the data section seems to be a reliable test.
- if string.format("%x",string.byte(response,position + 3)) ~= "d0" then
- return
- end
-
- local bytes = " "
- local len_response = string.len(response) - 2
-
- -- Parse response until the EXCSAT identifier is found. From here we should
- -- be able to find everything else.
- while (bytes ~= "1443") and (position <= len_response) do
- bytes = string.format("%x",string.byte(response,position +1)) .. string.format("%x",string.byte(response,position + 2))
- if bytes == nil then
- return
- end
- position = position + 2
- end
-
- if position >= len_response then
- -- If this section is true then this either not a valid response or
- -- it is in a format that we have not seen. Exit cleanly.
- return
- end
-
- -- ****************************************************************************
- -- Process the Server class section of the response packet
- -- ****************************************************************************
- local len_external_name, external_name = process_block(response, position, ebcdic2ascii)
-
- -- ****************************************************************************
- -- Process the Manager Level section of the response packet
- -- ****************************************************************************
- -- Move the position to the beginning of the current section
- position = position + len_external_name
-
- -- Get the length of the next block, Wireshark calls this "Manager-Level list"
- -- We are going to skip over this section
- local len_manager_level = string.format("%d",string.byte(response, position +1)) .. string.format("%d",string.byte(response,position + 2))
-
-
- -- ****************************************************************************
- -- Process the Server class section of the response packet
- -- ****************************************************************************
- -- Move the position to the beginning of the current section
- position = position + len_manager_level
- local len_server_class, server_class = process_block(response, position, ebcdic2ascii)
-
-
- -- ****************************************************************************
- -- Process the Server name section of the response packet
- -- ****************************************************************************
- -- Move the position to the beginning of the current section
- position = position + len_server_class
- local len_server_name, server_name = process_block(response, position, ebcdic2ascii)
-
- -- ****************************************************************************
- -- Process the Server version section of the response packet
- -- ****************************************************************************
- -- Move the position to the beginning of the current section
- position = position + len_server_name
-
- local len_server_version, server_version = process_block(response, position, ebcdic2ascii)
-
- if string.sub(server_version,1,3) == "SQL" then
- local major_version = string.sub(server_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(server_version,6,7)
- local hotfix = string.sub(server_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
-
- -- Set port information
- port.version.name = "ibm-db2"
- port.version.product = "IBM DB2 Database Server"
- port.version.name_confidence = 100
- nmap.set_port_state(host, port, "open")
- if server_class ~= nil then port.version.extrainfo = server_class end
-
- nmap.set_port_version(host, port, "hardmatched")
-
- -- Generate results
- local results = "DB2 Version: " .. server_version .. "\n"
- results = results .. "Server Platform: " .. server_class .. "\n"
- results = results .. "Instance Name: " .. server_name .. "\n"
- results = results .. "External Name: " .. external_name
-
- return results
-
-end
-
-
+description = [[
+Attempts to extract information from IBM DB2 Server instances. The script sends a
+DB2 EXCSAT (exchange server attributes) command packet and parses the response.
+]]
+
+---
+-- @output
+-- PORT STATE SERVICE
+-- 50000/tcp open ibm-db2
+-- | db2-info: DB2 Version: 8.02.9
+-- | Server Platform: QDB2/SUN
+-- | Instance Name: db2inst1
+-- |_ External Name: db2inst1db2agent00002B430
+
+author = "Patrik Karlsson"
+
+license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
+
+categories = {"safe", "discovery", "version"}
+
+require "stdnse"
+require "shortport"
+require "db2"
+
+-- Version 0.1
+-- Created 05/08/2010 - v0.1 - created by Patrik Karlsson
+
+--
+-- parseVersion was ripped from the old db2-info.nse written by Tom Sellers
+--
+
+portrule = shortport.port_or_service({50000,60000},"ibm-db2", "tcp", {"open", "open|filtered"})
+
+--- Converts the prodrel server string to a version string
+--
+-- @param server_version string containing the product release
+-- @return ver string containing the version information
+local function parseVersion( server_version )
+
+ if string.sub(server_version,1,3) == "SQL" then
+ local major_version = string.sub(server_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(server_version,6,7)
+ local hotfix = string.sub(server_version,8)
+ server_version = major_version .. "." .. minor_version .. "." .. hotfix
+ end
+
+ return server_version
+end
+
+action = function( host, port )
+
+ local db2helper = db2.Helper:new()
+ local status, response
+
+ status, response = db2helper:connect(host, port)
+ if( not(status) ) then
+ return response
+ end
+
+ status, response = db2helper:getServerInfo()
+ if( not(status) ) then
+ return response
+ end
+
+ db2helper:close()
+
+ -- Set port information
+ port.version.name = "ibm-db2"
+ port.version.product = "IBM DB2 Database Server"
+ port.version.name_confidence = 100
+ nmap.set_port_state(host, port, "open")
+ if response.srvclass ~= nil then port.version.extrainfo = response.srvclass end
+
+ nmap.set_port_version(host, port, "hardmatched")
+
+ -- Generate results
+ local results = "DB2 Version: " .. parseVersion(response.prodrel) .. "\n"
+ results = results .. "Server Platform: " .. response.srvclass .. "\n"
+ results = results .. "Instance Name: " .. response.srvname .. "\n"
+ results = results .. "External Name: " .. response.extname
+
+ return results
+end
\ No newline at end of file
diff --git a/scripts/script.db b/scripts/script.db
index 46e0d4b70..114b4ffb6 100644
--- a/scripts/script.db
+++ b/scripts/script.db
@@ -15,6 +15,7 @@ Entry { filename = "couchdb-databases.nse", categories = { "discovery", "safe",
Entry { filename = "couchdb-stats.nse", categories = { "discovery", "safe", } }
Entry { filename = "daap-get-library.nse", categories = { "discovery", "safe", } }
Entry { filename = "daytime.nse", categories = { "discovery", "safe", } }
+Entry { filename = "db2-brute.nse", categories = { "auth", "intrusive", } }
Entry { filename = "db2-das-info.nse", categories = { "discovery", "safe", "version", } }
Entry { filename = "db2-info.nse", categories = { "discovery", "safe", "version", } }
Entry { filename = "dhcp-discover.nse", categories = { "default", "discovery", "intrusive", } }