1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-14 19:59:02 +00:00

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]
This commit is contained in:
patrik
2010-05-18 21:11:38 +00:00
parent 940bdfc689
commit b830a036ad
5 changed files with 1095 additions and 356 deletions

View File

@@ -1,5 +1,10 @@
# Nmap Changelog ($Id$); -*-text-*- # 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 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. single connection and then exit, just like in normal listen mode.
Use the --keep-open option to get the old default inetd-like Use the --keep-open option to get the old default inetd-like

834
nselib/db2.lua Normal file
View File

@@ -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
-- <code>getServerInfo</code> 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:
--
-- <code>
-- db2helper = db2.Helper:new()
-- status, err = db2helper:connect(host, port)
-- status, res = db2helper:getServerInfo()
-- status, err = db2helper:close()
-- </code>
--
-- 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 <patrik@cqure.net>"
--
--
-- Version 0.1
-- Created 05/08/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
--
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 <code>extname</code>, <code>srvclass</code>,
-- <code>srvname</code> and <code>prodrel</code>
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
-- <code>recv</code> function will fetch bytes from the buffer and call
-- the <code>recieve_bytes</code> function of the underlying when there
-- are no more buffered bytes.
--
-- The <code>connect</code>, <code>close</code> and <code>send</code>
-- 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 <code>"tcp"</code>, <code>"udp"</code>, 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 <code>socket:receive_bytes</code> 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,
}

166
scripts/db2-brute.nse Normal file
View File

@@ -0,0 +1,166 @@
description = [[
Performs password guessing against IBM DB2
]]
---
-- @usage
-- nmap -p 50000 --script db2-brute <host>
--
-- @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 <patrik@cqure.net>
-- Revised 05/09/2010 - v0.2 - re-wrote as multi-threaded <patrik@cqure.net>
-- Revised 05/10/2010 - v0.3 - revised parallellised design <patrik@cqure.net>
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 <code>action</code>
-- @param port table with information as recieved by <code>action</code>
-- @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 "<empty>" ) )
end
end
end
condvar("broadcast")
end
--- Checks if the supplied database exists
--
-- @param host table with information as recieved by <code>action</code>
-- @param port table with information as recieved by <code>action</code>
-- @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=<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

View File

@@ -3,8 +3,6 @@ Attempts to extract information from IBM DB2 Server instances. The script sends
DB2 EXCSAT (exchange server attributes) command packet and parses the response. DB2 EXCSAT (exchange server attributes) command packet and parses the response.
]] ]]
-- rev 1.3 (2009-12-16)
--- ---
-- @output -- @output
-- PORT STATE SERVICE -- PORT STATE SERVICE
@@ -14,7 +12,7 @@ DB2 EXCSAT (exchange server attributes) command packet and parses the response.
-- | Instance Name: db2inst1 -- | Instance Name: db2inst1
-- |_ External Name: db2inst1db2agent00002B430 -- |_ External Name: db2inst1db2agent00002B430
author = "Tom Sellers" author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html" license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
@@ -22,287 +20,22 @@ categories = {"safe", "discovery", "version"}
require "stdnse" require "stdnse"
require "shortport" require "shortport"
require "db2"
-- Version 0.1
-- Created 05/08/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
--
-- 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"}) portrule = shortport.port_or_service({50000,60000},"ibm-db2", "tcp", {"open", "open|filtered"})
-- This function processes a section of the EXCSAT response packet --- Converts the prodrel server string to a version string
--
--@param response This is the data returned from the server as a result of the client query. -- @param server_version string containing the product release
--@param position This is the position within the response that this function will start processing from. -- @return ver string containing the version information
--@param ebcdic2ascii This is a table containing a conversion chart for returning the ASCII value of EBCDIC encoded HEX value. local function parseVersion( server_version )
--@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 if string.sub(server_version,1,3) == "SQL" then
local major_version = string.sub(server_version,4,5) local major_version = string.sub(server_version,4,5)
@@ -317,40 +50,40 @@ action = function(host, port)
server_version = major_version .. "." .. minor_version .. "." .. hotfix server_version = major_version .. "." .. minor_version .. "." .. hotfix
end end
-- Try to determine which of the two values (probe version vs script) has more return server_version
-- 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 end
local new_count = 0 action = function( host, port )
if server_version ~= nil then
_, new_count = string.gsub(server_version, "%.", "%.") local db2helper = db2.Helper:new()
local status, response
status, response = db2helper:connect(host, port)
if( not(status) ) then
return response
end end
if current_count < new_count then status, response = db2helper:getServerInfo()
port.version.version = server_version if( not(status) ) then
return response
end end
db2helper:close()
-- Set port information -- Set port information
port.version.name = "ibm-db2" port.version.name = "ibm-db2"
port.version.product = "IBM DB2 Database Server" port.version.product = "IBM DB2 Database Server"
port.version.name_confidence = 100 port.version.name_confidence = 100
nmap.set_port_state(host, port, "open") nmap.set_port_state(host, port, "open")
if server_class ~= nil then port.version.extrainfo = server_class end if response.srvclass ~= nil then port.version.extrainfo = response.srvclass end
nmap.set_port_version(host, port, "hardmatched") nmap.set_port_version(host, port, "hardmatched")
-- Generate results -- Generate results
local results = "DB2 Version: " .. server_version .. "\n" local results = "DB2 Version: " .. parseVersion(response.prodrel) .. "\n"
results = results .. "Server Platform: " .. server_class .. "\n" results = results .. "Server Platform: " .. response.srvclass .. "\n"
results = results .. "Instance Name: " .. server_name .. "\n" results = results .. "Instance Name: " .. response.srvname .. "\n"
results = results .. "External Name: " .. external_name results = results .. "External Name: " .. response.extname
return results return results
end end

View File

@@ -15,6 +15,7 @@ Entry { filename = "couchdb-databases.nse", categories = { "discovery", "safe",
Entry { filename = "couchdb-stats.nse", categories = { "discovery", "safe", } } Entry { filename = "couchdb-stats.nse", categories = { "discovery", "safe", } }
Entry { filename = "daap-get-library.nse", categories = { "discovery", "safe", } } Entry { filename = "daap-get-library.nse", categories = { "discovery", "safe", } }
Entry { filename = "daytime.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-das-info.nse", categories = { "discovery", "safe", "version", } }
Entry { filename = "db2-info.nse", categories = { "discovery", "safe", "version", } } Entry { filename = "db2-info.nse", categories = { "discovery", "safe", "version", } }
Entry { filename = "dhcp-discover.nse", categories = { "default", "discovery", "intrusive", } } Entry { filename = "dhcp-discover.nse", categories = { "default", "discovery", "intrusive", } }