1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-14 11:49:01 +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-*-
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

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

@@ -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 <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"})
--- 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

View File

@@ -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", } }