From b830a036adb16f644ef3046714bf711dbe62a91f Mon Sep 17 00:00:00 2001 From: patrik Date: Tue, 18 May 2010 21:11:38 +0000 Subject: [PATCH] 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] --- CHANGELOG | 5 + nselib/db2.lua | 834 ++++++++++++++++++++++++++++++++++++++++++ scripts/db2-brute.nse | 166 +++++++++ scripts/db2-info.nse | 445 +++++----------------- scripts/script.db | 1 + 5 files changed, 1095 insertions(+), 356 deletions(-) create mode 100644 nselib/db2.lua create mode 100644 scripts/db2-brute.nse 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", } }