diff --git a/CHANGELOG b/CHANGELOG
index ffaf1f8f0..91c6d6c6f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,9 +1,15 @@
# Nmap Changelog ($Id$); -*-text-*-
+o [NSE] Added a Oracle TNS library and two new scripts that make use of it.
+ The scripts are:
+ - oracle-brute uses the brute and tns library to perform password guessing
+ - oracle-enum-users attempts to determine valid Oracle user names
+ [Patrik]
+
o [NSE] Added a smallish Lotus Domino rpc library (nrpc.lua) and some Lotus
Domino oriented scripts:
- - domino-enum-users.nse guesses users and attempts to download ID files by
- exploiting (CVE-2006-5835).
+ - domino-enum-users guesses users and attempts to download ID files by
+ exploiting (CVE-2006-5835).
- domino-enum-passwords attempts to download Internet passwords and ID files
from the web server.
- domcon-brute performs password guessing against the remote console.
diff --git a/nselib/tns.lua b/nselib/tns.lua
new file mode 100644
index 000000000..83a20f9fa
--- /dev/null
+++ b/nselib/tns.lua
@@ -0,0 +1,1227 @@
+---
+-- TNS Library supporting a very limited subset of Oracle operations
+--
+-- Summary
+-- -------
+-- The library currently provides functionality to connect and authenticate
+-- to the Oracle database server. It has currently been tested against and
+-- known to work with Oracle 10G and Oracle 11G.
+--
+--
+-- Overview
+-- --------
+-- The library contains the following classes:
+--
+-- o Packet.*
+-- - The Packet classes contain specific packets and function to serialize
+-- them to strings that can be sent over the wire. Each class may also
+-- contain a function to parse the servers response.
+--
+-- o Comm
+-- - Implements a number of functions to handle communication over the
+-- the TNSSocket class.
+--
+-- o Crypt
+-- - Implements encryption algorithms and functions to support
+-- authentication with Oracle 10G and Oracle 11G.
+--
+-- o Helper
+-- - A helper class that provides easy access to the rest of the library
+--
+-- o TNSSocket
+-- - This is a copy of the DB2Socket class which provides fundamental
+-- buffering
+--
+--
+-- Example
+-- -------
+-- The following sample code illustrates how scripts can use the Helper class
+-- to interface the library:
+--
+--
+-- tnshelper = tns.Helper:new(host, port)
+-- status, err = tnshelper:Connect()
+-- status, res = tnshelper:Login("sys", "change_on_install")
+-- status, err = tnshelper:Close()
+--
+--
+-- Additional information
+-- ----------------------
+-- The implementation is based on the following documentation and through
+-- analysis of packet dumps:
+--
+-- o Oracle 10g TNS AES-128 authentication details (Massimiliano Montoro)
+-- x http://www.oxid.it/downloads/oracle_tns_aes128_check.txt
+-- o Oracle 11g TNS AES-192 authentication details (Massimiliano Montoro)
+-- x http://www.oxid.it/downloads/oracle_tns_aes192_check.txt
+-- o Initial analysis of Oracle native authentication version 11g
+-- (László Tóth)
+-- x http://www.soonerorlater.hu/index.khtml?article_id=512
+-- o Oracle native authentication version 9i and 10g (László Tóth)
+-- x http://www.soonerorlater.hu/index.khtml?article_id=511
+--
+-- This implementation is tested and known to work against:
+-- x Oracle 10g R2 on Windows
+-- x Oracle 11g on Linux
+-- x Oracle 11g R2 on Linux
+--
+-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
+-- @author "Patrik Karlsson "
+--
+-- @args tns.sid specifies the Oracle instance to connect to
+
+--
+-- Version 0.3
+-- Created 07/12/2010 - v0.1 - created by Patrik Karlsson
+-- Revised 07/21/2010 - v0.2 - made minor changes to support 11gR2 on Windows
+-- Revised 07/23/2010 - v0.3 - corrected incorrect example code in docs
+-- - removed ssl require
+
+require 'bin'
+
+module(... or "tns", package.seeall)
+
+-- Make sure we have SSL support
+local HAVE_SSL = false
+
+if pcall(require,'openssl') then
+ HAVE_SSL = true
+ math.randomseed( select(2, bin.unpack(">L", openssl.rand_bytes(8))))
+else
+ math.randomseed( os.time() )
+end
+
+
+-- Oracle version constants
+ORACLE_VERSION_10G = 313
+ORACLE_VERSION_11G = 314
+
+AuthOptions =
+{
+
+ new = function( self )
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+
+ o.auth_user = nil
+ o.auth_term = "pts/" .. math.random(255)
+ o.auth_prog = ("sqlplus@nmap_%d (TNS V1-V3)"):format(math.random(32768))
+ o.auth_machine = "nmap_target"
+ o.auth_pid = "" .. math.random(32768)
+ o.auth_sid = "nmap_" .. math.random(32768)
+
+ return o
+ end,
+
+}
+
+-- Packet class table
+--
+-- Each Packet type SHOULD implement:
+-- o tns_type - A variable indicating the TNS Type of the Packet
+-- o toString - A function that serializes the object to string
+--
+-- Each Packet MAY also optionally implement:
+-- o parseResponse
+-- x An optional function that parses the servers response
+-- x The function should return status and an undefined second return value
+--
+Packet = {}
+
+-- Contains the TNS header and basic functions for decoding and reading the
+-- TNS packet.
+Packet.TNS = {
+
+ checksum = 0,
+ hdr_checksum = 0,
+ length = 0,
+ reserved = 0,
+
+ Type =
+ {
+ CONNECT = 1,
+ ACCEPT = 2,
+ REFUSE = 4,
+ DATA = 6,
+ RESEND = 11,
+ MARKER = 12,
+ },
+
+ new = function( self, sock )
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.socket = sock
+ return o
+ end,
+
+ --- Read a TNS packet of the socket
+ --
+ -- @return true on success, false on failure
+ -- @return err string containing error message on failure
+ recv = function( self )
+ local _
+ local status, data = self.socket:recv( 2 )
+
+ if ( not(status) ) then
+ return status, data
+ end
+
+ _, self.length = bin.unpack(">S", data )
+
+ status, data = self.socket:recv( 6 ) -- self.length - 2 )
+ if ( not(status) ) then
+ return status, data
+ end
+
+ _, self.checksum, self.type, self.reserved, self.hdr_checksum = bin.unpack(">SCCS", data)
+
+ status, data = self.socket:recv( self.length - 8)
+ if ( status ) then
+ self.data = data
+ end
+
+ return true
+ end,
+
+ --- Converts the TNS packet to string suitable to be sent over the socket
+ --
+ -- @return string containing the TNS packet
+ toString = function( self )
+ local data = bin.pack(">SSCCSA", self.length, self.checksum, self.type,
+ self.reserved, self.hdr_checksum, self.data )
+ return data
+ end,
+
+}
+
+-- Initiates the connection to the listener
+Packet.Connect = {
+
+ CONN_STR = [[
+ (DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=%s)(PORT=%d))
+ (CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=%s)(CID=
+ (PROGRAM=sqlplus)(HOST=%s)(USER=nmap))))]],
+
+ version = 314,
+ version_comp = 300,
+ svc_options = 0x0c41,
+ sess_dus = 8192,
+ max_trans_dus = 32767,
+ nt_proto_char = 0x7f08,
+ line_turnaround = 0,
+ value_of_1_in_hw = 0x0100,
+ conn_data_len = 0,
+ conn_data_offset = 58,
+ conn_data_max_recv = 512,
+ conn_data_flags_0 = 0x41,
+ conn_data_flags_1 = 0x41,
+ trace_cross_1 = 0,
+ trace_cross_2 = 0,
+ trace_unique_conn = 0,
+ tns_type = Packet.TNS.Type.CONNECT,
+
+ new = function( self, rhost, rport, dbinstance )
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.rhost = rhost
+ o.rport = rport
+ o.conn_data = Packet.Connect.CONN_STR:format( rhost, rport, dbinstance, rhost )
+ o.dbinstance = dbinstance:upper()
+ return o
+ end,
+
+ setCmd = function( self, cmd )
+ local tmp = [[
+ (DESCRIPTION=(CONNECT_DATA=(CID=(PROGRAM=)(HOST=%s)(USER=nmap))(COMMAND=%s)(ARGUMENTS=64)(SERVICE=%s:%d)(VERSION=185599488)))
+ ]]
+ self.conn_data = tmp:format( self.rhost, cmd, self.rhost, self.rport )
+ end,
+
+ --- Parses the server response from the CONNECT
+ --
+ -- @param tns Packet.TNS containing the TNS packet received from the
+ -- server
+ -- @return true on success, false on failure
+ -- @return version number containing the version supported by the
+ -- server or an error message on failure
+ parseResponse = function( self, tns )
+ local pos, version
+
+ if ( tns.type ~= Packet.TNS.Type.ACCEPT ) then
+ if ( tns.data:match("ERR=12514") ) then
+ return false, ("TNS: The listener could not resolve \"%s\""):format(self.dbinstance)
+ end
+ return false, "The server did not respond with an ACCEPT packet"
+ end
+
+ pos, version = bin.unpack(">S", tns.data )
+ return true, version
+ end,
+
+ --- Converts the CONNECT packet to string
+ --
+ -- @return string containing the packet
+ toString = function( self )
+ self.conn_data_len = #self.conn_data
+
+ return bin.pack(">SSSSSSSSSSICCIILLA", self.version, self.version_comp, self.svc_options,
+ self.sess_dus, self.max_trans_dus, self.nt_proto_char,
+ self.line_turnaround, self.value_of_1_in_hw, self.conn_data_len,
+ self.conn_data_offset, self.conn_data_max_recv, self.conn_data_flags_0,
+ self.conn_data_flags_1, self.trace_cross_1, self.trace_cross_2,
+ self.trace_unique_conn, 0, self.conn_data )
+ end,
+
+
+}
+
+-- A TNS data packet, one of the most common packets
+Packet.Data = {
+
+ flag = 0,
+
+ new = function( self, sock, data )
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.TNS = Packet.TNS:new( sock )
+ o.TNS.type = Packet.TNS.Type.DATA
+ o.socket = sock
+ o.data = data
+ return o
+ end,
+
+ --- Converts the DATA packet to string
+ --
+ -- @return string containing the packet
+ toString = function( self )
+ local data = bin.pack( ">S", self.flag ) .. self.data
+
+ self.TNS.length = #data + 8
+
+ return self.TNS:toString() .. data
+ end,
+
+}
+
+-- Packet received by the server to indicate errors or end of
+-- communication.
+Packet.Attention = {
+
+ tns_type = Packet.TNS.Type.MARKER,
+
+ new = function( self, typ, data )
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.att_type = typ
+ o.data = data
+ return o
+ end,
+
+ --- Converts the MARKER packet to string
+ --
+ -- @return string containing the packet
+ toString = function( self )
+ return bin.pack( ">C", self.att_type ) .. self.data
+ end,
+
+}
+
+-- Packet initializing challenge response authentication
+Packet.PreAuth = {
+
+ tns_type = Packet.TNS.Type.DATA,
+ flags = 0,
+
+ param_order = {
+ [1] = { ["AUTH_TERMINAL"] = "auth_term" },
+ [2] = { ["AUTH_PROGRAM_NM"] = "auth_prog" },
+ [3] = { ["AUTH_MACHINE"] = "auth_machine" },
+ [4] = { ["AUTH_PID"] = "auth_pid" },
+ [5] = { ["AUTH_SID"] = "auth_sid" }
+ },
+
+
+ --- Creates a new PreAuth packet
+ --
+ -- @param user string containing the user name
+ -- @return a new instance of Packet.PreAuth
+ new = function(self, user, options)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.auth_user = user
+ o.auth_options = options
+ return o
+ end,
+
+ --- Converts a parameter to a string representation
+ --
+ -- @param name string containing the parameter name
+ -- @param value string containing the parameter value
+ -- @return string containing the parameter key and value
+ paramToString = function( self, param_name, param_value )
+ return bin.pack(">CIACIAI", #param_name, #param_name, param_name, #param_value, #param_value, param_value, 0 )
+ end,
+
+ --- Converts the DATA packet to string
+ --
+ -- @return string containing the packet
+ toString = function( self )
+ local data = bin.pack("CIAII", #param_name, #param_name, param_name, #param_value, 0 )
+ else
+ return bin.pack(">CIACIAI", #param_name, #param_name, param_name, #param_value, #param_value, param_value, 0 )
+ end
+ end,
+
+ --- Converts the DATA packet to string
+ --
+ -- @return string containing the packet
+ toString = function( self )
+
+ local sess_id = select(2, bin.unpack("H16", openssl.rand_pseudo_bytes(16)))
+ local data = bin.pack(">SHCHpHAHAH", self.flags, "037303feffffff",
+ #self.user, "00000001010000feffffff12000000fefffffffeffffff",
+ self.user, "0c0000000c415554485f534553534b455960000000fe40",
+ self.auth_sesskey, "00010000000d0000000d415554485f50415353574f52444000000040",
+ self.auth_pass, "00000000")
+
+ for k, v in ipairs( self.param_order ) do
+ if ( v['def'] ) then
+ data = data .. self:paramToString( v['key'], v['def'])
+ elseif ( self.auth_options[ v['var'] ] ) then
+ data = data .. self:paramToString( v['key'], self.auth_options[ v['var'] ] )
+ elseif ( self[ v['var'] ] ) then
+ data = data .. self:paramToString( v['key'], self[ v['var'] ] )
+ end
+ end
+
+
+ return data
+ end
+}
+
+Packet.SNS = {
+
+ tns_type = Packet.TNS.Type.DATA,
+ flags = 0,
+
+ new = function(self)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+
+ --- Converts the DATA packet to string
+ --
+ -- @return string containing the packet
+ toString = function( self )
+ return bin.pack("SH", self.flags, "deadbeef00920b1006000004000004000300000000000400050b10060000080001000015cb353abecb00120001" ..
+ "deadbeef00030000000400040001000100020001000300000000000400050b10060000020003e0e100020006fc" ..
+ "ff0002000200000000000400050b100600000c0001001106100c0f0a0b08020103000300020000000000040005" ..
+ "0b10060000030001000301" )
+ end,
+}
+
+-- Packet containing protocol negotiation
+Packet.ProtoNeg = {
+
+ tns_type = Packet.TNS.Type.DATA,
+ flags = 0,
+
+ new = function(self)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ --- Converts the DATA packet to string
+ --
+ -- @return string containing the packet
+ toString = function( self )
+ local pfx = bin.pack(">SH", self.flags, "0106050403020100")
+ return pfx .. "Linuxi386/Linux-2.0.34-8.1.0\0"
+ end,
+
+ --- Parses and verifies the server response
+ --
+ -- @param tns Packet.TNS containing the response from the server
+ parseResponse = function( self, tns )
+ local flags, neg, ver, srv, pos, _
+
+ pos, flags, neg, ver, _, srv = bin.unpack(">SCCCz", tns.data)
+ if ( neg ~= 1 ) then
+ return false, "Error protocol negotiation failed"
+ end
+
+ if ( ver ~= 6 ) then
+ return false, ("Error protocol version (%d) not supported"):format(ver)
+ end
+
+ return true, srv
+ end
+
+}
+
+Packet.Unknown1 = {
+
+ tns_type = Packet.TNS.Type.DATA,
+ flags = 0,
+
+ --- Creates a new Packet.Unknown1
+ --
+ -- @param version containing the version of the packet to send
+ -- @return new instance of Packet.Unknown1
+ new = function(self, os)
+ local o = {}
+ setmetatable(o, self)
+ o.os = os
+ self.__index = self
+ return o
+ end,
+
+ --- Converts the DATA packet to string
+ --
+ -- @return string containing the packet
+ toString = function( self )
+
+ if ( self.os:match("IBMPC/WIN_NT[-]8.1.0") ) then
+ return bin.pack(">SH", self.flags, [[
+ 02b200b2004225060101010d010105010101010101017fff0309030301007f0
+ 11fff010301013f01010500010702010000180001800000003c3c3c80000000
+ d007000100010001000000020002000a00000008000800010000000c000c000
+ a00000017001700010000001800180001000000190019001800190001000000
+ 1a001a0019001a00010000001b001b000a001b00010000001c001c0016001c0
+ 0010000001d001d0017001d00010000001e001e0017001e00010000001f001f
+ 0019001f0001000000200020000a00200001000000210021000a00210001000
+ 0000a000a00010000000b000b00010000002800280001000000290029000100
+ 000075007500010000007800780001000001220122000100000123012300010
+ 12300010000012401240001000001250125000100000126012600010000012a
+ 012a00010000012b012b00010000012c012c00010000012d012d00010000012
+ e012e00010000012f012f000100000130013000010000013101310001000001
+ 320132000100000133013300010000013401340001000001350135000100000
+ 136013600010000013701370001000001380138000100000139013900010000
+ 013b013b00010000013c013c00010000013d013d00010000013e013e0001000
+ 0013f013f000100000140014000010000014101410001000001420142000100
+ 000143014300010000014701470001000001480148000100000149014900010
+ 000014b014b00010000014d014d00010000014e014e00010000014f014f0001
+ 000001500150000100000151015100010000015201520001000001530153000
+ 100000154015400010000015501550001000001560156000100000157015700
+ 0101570001000001580158000100000159015900010000015a015a000100000
+ 15c015c00010000015d015d0001000001620162000100000163016300010000
+ 0167016700010000016b016b00010000017c017c0001014200010000017d017
+ d00010000017e017e00010000017f017f000100000180018000010000018101
+ 810001000001820182000100000183018300010000018401840001000001850
+ 18500010000018601860001000001870187000100000189018900010000018a
+ 018a00010000018b018b00010000018c018c00010000018d018d00010000018
+ e018e00010000018f018f000100000190019000010000019101910001000001
+ 940194000101250001000001950195000100000196019600010000019701970
+ 0010000019d019d00010000019e019e00010000019f019f0001000001a001a0
+ 0001000001a101a10001000001a201a20001000001a301a30001000001a401a
+ 40001000001a501a50001000001a601a60001000001a701a70001000001a801
+ a80001000001a901a90001000001aa01aa0001000001ab01ab0001000001ad0
+ 1ad0001000001ae01ae0001000001af01af0001000001b001b00001000001b1
+ 01b10001000001c101c10001000001c201c2000101250001000001c601c6000
+ 1000001c701c70001000001c801c80001000001c901c90001000001ca01ca00
+ 01019f0001000001cb01cb000101a00001000001cc01cc000101a2000100000
+ 1cd01cd000101a30001000001ce01ce000101b10001000001cf01cf00010122
+ 0001000001d201d20001000001d301d3000101ab0001000001d401d40001000
+ 001d501d50001000001d601d60001000001d701d70001000001d801d8000100
+ 0001d901d90001000001da01da0001000001db01db0001000001dc01dc00010
+ 00001dd01dd0001000001de01de0001000001df01df0001000001e001e00001
+ 000001e101e10001000001e201e20001000001e301e30001016b0001000001e
+ 401e40001000001e501e50001000001e601e60001000001ea01ea0001000001
+ eb01eb0001000001ec01ec0001000001ed01ed0001000001ee01ee000100000
+ 1ef01ef0001000001f001f00001000001f201f20001000001f301f300010000
+ 01f401f40001000001f501f50001000001f601f60001000001fd01fd0001000
+ 001fe01fe000100000201020100010000020202020001000002040204000100
+ 000205020500010000020602060001000002070207000100000208020800010
+ 0000209020900010000020a020a00010000020b020b00010000020c020c0001
+ 0000020d020d00010000020e020e00010000020f020f0001000002100210000
+ 100000211021100010000021202120001000002130213000100000214021400
+ 010000021502150001000002160216000100000217021700010000021802180
+ 00100000219021900010000021a021a00010000021b021b00010000021c021c
+ 00010000021d021d00010000021e021e00010000021f021f000100000220022
+ 000010000022102210001000002220222000100000223022300010000022402
+ 240001000002250225000100000226022600010000022702270001000002280
+ 228000100000229022900010000022a022a00010000022b022b00010000022c
+ 022c00010000022d022d00010000022e022e00010000022f022f00010000023
+ 102310001000002320232000100000233023300010000023402340001000002
+ 3702370001000002380238000100000239023900010000023a023a000100000
+ 23b023b00010000023c023c00010000023d023d00010000023e023e00010000
+ 023f023f0001000002400240000100000241024100010000024202420001000
+ 002430243000100000244024400010000
+ ]])
+ else
+ return bin.pack(">SH", self.flags, "02b200b2004225060101010d010105010101010101017fff0309030301007f011" ..
+ "fff010301013f01010500010702010000180001800000003c3c3c80000000d007")
+ end
+ end,
+
+}
+
+
+--- This packet is only used by Oracle10 and older
+Packet.Unknown2 = {
+
+ tns_type = Packet.TNS.Type.DATA,
+ flags = 0,
+
+ new = function(self)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ --- Converts the DATA packet to string
+ --
+ -- @return string containing the packet
+ toString = function( self )
+ return bin.pack(">SH", self.flags, [[
+ 024502450001000002460246000100000247024700010000024802480001000
+ 0024902490001000000030002000a000000040002000a000000050001000100
+ 0000060002000a000000070002000a00000009000100010000000d0000000e0
+ 000000f00170001000000100000001100000012000000130000001400000015
+ 000000160000002700780001015d0001012600010000003a003a00010000004
+ 40002000a00000045000000460000004a006d00010000004c0000005b000200
+ 0a0000005e000100010000005f0017000100000060006000010000006100600
+ 001000000640064000100000065006500010000006600660001000000680000
+ 00690000006a006a00010000006c006d00010000006d006d00010000006e006
+ f00010000006f006f0001000000700070000100000071007100010000007200
+ 720001000000730073000100000074006600010000007600000077000000790
+ 07900010000007a007a00010000007b007b0001000000880000009200920001
+ 0000009300930001000000980002000a000000990002000a0000009a0002000
+ a0000009b000100010000009c000c000a000000ac0002000a000000b200b200
+ 01000000b300b30001000000b400b40001000000b500b50001000000b600b60
+ 001000000b700b70001000000b8000c000a000000b900b20001000000ba00b3
+ 0001000000bb00b40001000000bc00b50001000000bd00b60001000000be00b
+ 70001000000bf000000c0000000c300700001000000c400710001000000c500
+ 720001000000d000d00001000000d1000000e700e70001000000e800e700010
+ 00000e900e90001000000f1006d0001000002030203000100000000
+ ]])
+ end,
+
+}
+
+-- Signals that we're about to close the connection
+Packet.EOF = {
+
+ tns_type = Packet.TNS.Type.DATA,
+ flags = 0x0040,
+
+ new = function(self)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ --- Converts the DATA packet to string
+ --
+ -- @return string containing the packet
+ toString = function( self )
+ return bin.pack(">S", self.flags )
+ end
+}
+
+-- The TNS communication class uses the TNSSocket to transmit data
+Comm = {
+
+ --- Creates a new instance of the Comm class
+ --
+ -- @param socket containing a TNSSocket
+ -- @return new instance of Comm
+ new = function(self, socket)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.tnssocket = socket
+ return o
+ end,
+
+ --- Attemts to send a TNS packet over the socket
+ --
+ -- @param pkt containing an instance of a Packet.*
+ -- @return Status (true or false).
+ -- @return Error code (if status is false).
+ sendTNSPacket = function( self, pkt )
+
+ local tns = Packet.TNS:new( self.tnssocket )
+ tns.type = pkt.tns_type
+ tns.data = pkt:toString()
+ tns.length = #tns.data + 8
+
+ -- buffer incase of RESEND
+ self.pkt = pkt
+
+ return self.tnssocket:send( tns:toString() )
+ end,
+
+ --- Handles communication when a MARKER packet is recieved and retrieves
+ -- the following error message
+ --
+ -- @return false always to indicate that an error occured
+ -- @return msg containing the error message
+ handleMarker = function( self )
+ local status, tns = self:recvTNSPacket()
+ local pos, msg, b1
+
+ if ( not(status) or tns.type ~= Packet.TNS.Type.MARKER ) then
+ return false, "ERROR: failed to handle marker sent by server"
+ end
+
+ -- send our marker
+ status = self:sendTNSPacket( Packet.Attention:new( 1, bin.pack("H", "0002") ) )
+ if ( not(status) ) then
+ return false, "ERROR: failed to send marker to server"
+ end
+
+ status, tns = self:recvTNSPacket()
+ if ( not(status) or tns.type ~= Packet.TNS.Type.DATA ) then
+ return false, "ERROR: expecting DATA packet"
+ end
+
+ -- check if byte 12 is set or not, this should help us distinguish the offset
+ -- to the error message in Oracle 10g and 11g
+ pos, b1 = bin.unpack("C", tns.data, 10)
+
+ if( b1 == 1) then
+ pos = 99
+ else
+ pos = 69
+ end
+
+ -- fetch the oracle error and return it
+ pos, msg = bin.unpack("p", tns.data, pos )
+
+ return false, msg
+ end,
+
+ --- Recieves a TNS packet and handles TNS-resends
+ --
+ -- @return status true on success, false on failure
+ -- @return tns Packet.TNS containing the recieved packet or err on failure
+ recvTNSPacket = function( self )
+ local tns = Packet.TNS:new( self.tnssocket )
+ local retries = 5
+
+ repeat
+ local status = tns:recv()
+ if ( not(status) ) then
+ if ( retries == 0 ) then
+ return false, "ERROR: recvTNSPacket failed to receive TNS headers"
+ end
+ retries = retries - 1
+ elseif ( tns.type == Packet.TNS.Type.RESEND ) then
+ self:sendTNSPacket( self.pkt )
+ end
+ until ( status and tns.type ~= Packet.TNS.Type.RESEND )
+
+ return true, tns
+ end,
+
+ --- Sends a TNS packet and recieves (and handles) the response
+ --
+ -- @param pkt containingt the Packet.* to send to the server
+ -- @return status true on success, false on failure
+ -- @return the parsed response as return from the respective parseResponse
+ -- function or error message if status was false
+ exchTNSPacket = function( self, pkt )
+ local status = self:sendTNSPacket( pkt )
+ local tns, response
+
+ if ( not(status) ) then
+ return false, "sendTNSPacket failed"
+ end
+
+ status, tns = self:recvTNSPacket()
+ if ( not(status) ) then
+ return false, tns
+ end
+
+ --- handle TNS MARKERS
+ if ( tns.type == Packet.TNS.Type.MARKER ) then
+ return self:handleMarker()
+ end
+
+ if ( pkt.parseResponse ) then
+ status, response = pkt:parseResponse( tns )
+ end
+
+ return status, response
+ end
+
+}
+
+--- Class that handles all Oracle encryption
+Crypt = {
+
+ -- Test function, not currently in use
+ Decrypt11g = function(self, c_sesskey, s_sesskey, auth_password, pass, salt )
+ local combined_sesskey = ""
+ local sha1 = openssl.sha1(pass .. salt) .. "\0\0\0\0"
+ local auth_sesskey = s_sesskey
+ local auth_sesskey_c = c_sesskey
+ local server_sesskey = openssl.decrypt( "aes-192-cbc", sha1, nil, auth_sesskey )
+ local client_sesskey = openssl.decrypt( "aes-192-cbc", sha1, nil, auth_sesskey_c )
+
+ combined_sesskey = ""
+ for i=17, 40 do
+ combined_sesskey = combined_sesskey .. string.char( bit.bxor( string.byte(server_sesskey, i), string.byte(client_sesskey,i) ) )
+ end
+ combined_sesskey = ( openssl.md5( combined_sesskey:sub(1,16) ) .. openssl.md5( combined_sesskey:sub(17) ) ):sub(1, 24)
+
+ local p = openssl.decrypt( "aes-192-cbc", combined_sesskey, nil, auth_password, false )
+ return p:sub(17)
+ end,
+
+ --- Creates an Oracle 10G password hash
+ --
+ -- @param username containing the Oracle user name
+ -- @param password containing the Oracle user password
+ -- @return hash containing the Oracle hash
+ HashPassword10g = function( self, username, password )
+ local uspw = ( username .. password ):gsub("(%w)", "\0%1")
+ local key = bin.pack("H", "0123456789abcdef")
+ local enc, iv2, hash
+
+ -- do padding
+ if ( #uspw % 8 > 0 ) then
+ for i=1,(8-(#uspw % 8)) do
+ uspw = uspw .. "\0"
+ end
+ end
+
+ iv2 = openssl.encrypt( "DES-CBC", key, nil, uspw, false ):sub(-8)
+ enc = openssl.encrypt( "DES-CBC", iv2, nil, uspw, false ):sub(-8)
+ return enc
+ end,
+
+ -- Test function, not currently in use
+ Decrypt10g = function(self, user, pass, srv_sesskey_enc )
+ local pwhash = self:HashPassword10g( user:upper(), pass:upper() ) .. "\0\0\0\0\0\0\0\0"
+ local cli_sesskey_enc = bin.pack("H", "7B244D7A1DB5ABE553FB9B7325110024911FCBE95EF99E7965A754BC41CF31C0")
+ local srv_sesskey = openssl.decrypt( "AES-128-CBC", pwhash, nil, srv_sesskey_enc )
+ local cli_sesskey = openssl.decrypt( "AES-128-CBC", pwhash, nil, cli_sesskey_enc )
+ local auth_pass = bin.pack("H", "4C5E28E66B6382117F9D41B08957A3B9E363B42760C33B44CA5D53EA90204ABE" )
+ local combined_sesskey = ""
+ local pass
+
+ for i=17, 32 do
+ combined_sesskey = combined_sesskey .. string.char( bit.bxor( string.byte(srv_sesskey, i), string.byte(cli_sesskey, i) ) )
+ end
+ combined_sesskey= openssl.md5( combined_sesskey )
+
+ pass = openssl.decrypt( "AES-128-CBC", combined_sesskey, nil, auth_pass ):sub(17)
+
+ print( select(2, bin.unpack("H" .. #srv_sesskey, srv_sesskey )))
+ print( select(2, bin.unpack("H" .. #cli_sesskey, cli_sesskey )))
+ print( select(2, bin.unpack("H" .. #combined_sesskey, combined_sesskey )))
+ print( "pass=" .. pass )
+ end,
+
+ --- Performs the relevant encryption needed for the Oracle 10g response
+ --
+ -- @param user containing the Oracle user name
+ -- @param pass containing the Oracle user password
+ -- @param srv_sesskey_enc containing the encrypted server session key as
+ -- recieved from the PreAuth packet
+ -- @return cli_sesskey_enc the encrypted client session key
+ -- @return auth_pass the encrypted Oracle password
+ Encrypt10g = function( self, user, pass, srv_sesskey_enc )
+
+ local pwhash = self:HashPassword10g( user:upper(), pass:upper() ) .. "\0\0\0\0\0\0\0\0"
+ -- We're currently using a static client session key, this should
+ -- probably be changed to a random value in the future
+ local cli_sesskey = bin.pack("H", "FAF5034314546426F329B1DAB1CDC5B8FF94349E0875623160350B0E13A0DA36")
+ local srv_sesskey = openssl.decrypt( "AES-128-CBC", pwhash, nil, srv_sesskey_enc )
+ local cli_sesskey_enc = openssl.encrypt( "AES-128-CBC", pwhash, nil, cli_sesskey )
+ -- This value should really be random, not this static cruft
+ local rnd = bin.pack("H", "4C31AFE05F3B012C0AE9AB0CDFF0C508")
+ local combined_sesskey = ""
+ local auth_pass
+
+ for i=17, 32 do
+ combined_sesskey = combined_sesskey .. string.char( bit.bxor( string.byte(srv_sesskey, i), string.byte(cli_sesskey, i) ) )
+ end
+ combined_sesskey= openssl.md5( combined_sesskey )
+
+ auth_pass = openssl.encrypt("AES-128-CBC", combined_sesskey, nil, rnd .. pass, true )
+
+ auth_pass = select(2, bin.unpack("H" .. #auth_pass, auth_pass))
+ cli_sesskey_enc = select(2, bin.unpack("H" .. #cli_sesskey_enc, cli_sesskey_enc))
+ return cli_sesskey_enc, auth_pass
+ end,
+
+ --- Performs the relevant encryption needed for the Oracle 11g response
+ --
+ -- @param pass containing the Oracle user password
+ -- @param srv_sesskey_enc containing the encrypted server session key as
+ -- recieved from the PreAuth packet
+ -- @param auth_vrfy_data containing the password salt as recieved from the
+ -- PreAuth packet
+ -- @return cli_sesskey_enc the encrypted client session key
+ -- @return auth_pass the encrypted Oracle password
+ Encrypt11g = function( self, pass, srv_sesskey_enc, auth_vrfy_data )
+
+ -- This value should really be random, not this static cruft
+ local rnd = openssl.rand_pseudo_bytes(16)
+ local cli_sesskey = openssl.rand_pseudo_bytes(40) .. bin.pack("H", "0808080808080808")
+ local pw_hash = openssl.sha1(pass .. auth_vrfy_data) .. "\0\0\0\0"
+ local srv_sesskey = openssl.decrypt( "aes-192-cbc", pw_hash, nil, srv_sesskey_enc )
+ local auth_password
+ local cli_sesskey_enc
+ local combined_sesskey = ""
+ local data = ""
+
+ for i=17, 40 do
+ combined_sesskey = combined_sesskey .. string.char( bit.bxor( string.byte(srv_sesskey, i), string.byte(cli_sesskey, i) ) )
+ end
+ combined_sesskey = ( openssl.md5( combined_sesskey:sub(1,16) ) .. openssl.md5( combined_sesskey:sub(17) ) ):sub(1, 24)
+
+ cli_sesskey_enc = openssl.encrypt( "aes-192-cbc", pw_hash, nil, cli_sesskey )
+ cli_sesskey_enc = select(2,bin.unpack("H" .. #cli_sesskey_enc, cli_sesskey_enc))
+ cli_sesskey_enc = cli_sesskey_enc:sub(1, 64) .. " " .. cli_sesskey_enc:sub(65)
+
+ auth_password = openssl.encrypt( "aes-192-cbc", combined_sesskey, nil, rnd .. pass, true )
+ auth_password = select(2, bin.unpack("H" .. #auth_password, auth_password))
+
+ return cli_sesskey_enc, auth_password
+ end,
+
+}
+
+Helper = {
+
+ new = function(self, host, port, instance )
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.host = host
+ o.port = port
+ o.tnssocket = TNSSocket:new()
+ o.dbinstance = instance or nmap.registry.args['tns.sid'] or "orcl"
+ return o
+ end,
+
+ --- Connects and performs protocol negotiation with the Oracle server
+ --
+ -- @return true on success, false on failure
+ -- @return err containing error message when status is false
+ Connect = function( self )
+ local status, data = self.tnssocket:connect( self.host.ip, self.port.number, "tcp" )
+ local conn, packet, tns
+
+ if( not(status) ) then
+ return status, data
+ end
+
+ self.comm = Comm:new( self.tnssocket )
+
+ status, self.version = self.comm:exchTNSPacket( Packet.Connect:new( self.host.ip, self.port.number, self.dbinstance ) )
+ if ( not(status) ) then
+ return false, self.version
+ end
+
+ if ( self.version ~= ORACLE_VERSION_11G and self.version ~= ORACLE_VERSION_10G ) then
+ return false, ("Unsupported Oracle Version (%d)"):format(self.version)
+ end
+
+ status = self.comm:exchTNSPacket( Packet.SNS:new( self.version ) )
+ if ( not(status) ) then
+ return false, "ERROR: Helper.Connect failed"
+ end
+
+ status, self.os = self.comm:exchTNSPacket( Packet.ProtoNeg:new( self.version ) )
+ if ( not(status) ) then
+ return false, data
+ end
+
+ if ( self.os:match("IBMPC/WIN_NT[-]8.1.0") ) then
+ status = self.comm:sendTNSPacket( Packet.Unknown1:new( self.os ) )
+ if ( not(status) ) then
+ return false, "ERROR: Helper.Connect failed"
+ end
+ status, data = self.comm:sendTNSPacket( Packet.Unknown2:new( ) )
+ if ( not(status) ) then
+ return false, data
+ end
+ status, data = self.comm:recvTNSPacket( Packet.Unknown2:new( ) )
+ if ( not(status) ) then
+ return false, data
+ end
+ -- Oracle 10g under Windows needs this additional read, there's
+ -- probably a better way to detect this by analysing the packets
+ -- further.
+ if ( self.version == ORACLE_VERSION_10G ) then
+ status, data = self.comm:recvTNSPacket( Packet.Unknown2:new( ) )
+ if ( not(status) ) then
+ return false, data
+ end
+ end
+
+ else
+ status = self.comm:exchTNSPacket( Packet.Unknown1:new( self.os ) )
+ if ( not(status) ) then
+ return false, "ERROR: Helper.Connect failed"
+ end
+
+ end
+
+ return true
+ end,
+
+ --- Sends a command to the TNS lsnr
+ -- It currently accepts and tries to send all commands recieved
+ --
+ -- @param cmd string containing the command to send to the server
+ -- @return data string containing the result recieved from the server
+ lsnrCtl = function( self, cmd )
+ local status, data = self.tnssocket:connect( self.host.ip, self.port.number, "tcp" )
+ local conn, packet, tns, pkt
+
+ if( not(status) ) then
+ return status, data
+ end
+
+ self.comm = Comm:new( self.tnssocket )
+
+ pkt = Packet.Connect:new( self.host.ip, self.port.number, self.dbinstance )
+ pkt:setCmd(cmd)
+
+ if ( not(self.comm:exchTNSPacket( pkt )) ) then
+ return false, self.version
+ end
+
+ data = ""
+ repeat
+ status, tns = self.comm:recvTNSPacket()
+ if ( not(status) ) then
+ self:Close()
+ return status, tns
+ end
+ local _, flags = bin.unpack(">S", tns.data )
+ data = data .. tns.data:sub(3)
+ until ( flags ~= 0 )
+ self:Close()
+
+ return true, data
+ end,
+
+ --- Authenticates to the database
+ --
+ -- @param user containing the Oracle user name
+ -- @param pass containing the Oracle user password
+ -- @return true on success, false on failure
+ -- @return err containing error message when status is false
+ Login = function( self, user, password )
+
+ local data, packet, status, tns, parser
+ local sesskey_enc, auth_pass, auth
+ local auth_options = AuthOptions:new()
+
+ status, auth = self.comm:exchTNSPacket( Packet.PreAuth:new( user, auth_options ) )
+ if ( not(status) ) then
+ return false, auth
+ end
+
+ -- Check what version of the DB to authenticate against AND verify whether
+ -- case sensitive login is enabled or not. In case-sensitive mode the salt
+ -- is longer, so we check the length of auth["AUTH_VFR_DATA"]
+ if ( self.version == ORACLE_VERSION_11G and #auth["AUTH_VFR_DATA"] > 2 ) then
+ sesskey_enc, auth_pass = Crypt:Encrypt11g( password, bin.pack( "H", auth["AUTH_SESSKEY"] ), bin.pack("H", auth["AUTH_VFR_DATA"] ) )
+ else
+ sesskey_enc, auth_pass = Crypt:Encrypt10g( user, password, bin.pack( "H", auth["AUTH_SESSKEY"] ) )
+ end
+
+ status, data = self.comm:exchTNSPacket( Packet.Auth:new( user, auth_options, sesskey_enc, auth_pass ) )
+ if ( not(status) ) then
+ return false, data
+ end
+
+ return true;
+ end,
+
+ --- Ends the Oracle communication
+ Close = function( self )
+ -- We should probably stick some slick sqlplus termination stuff in here
+ local status = self.comm:sendTNSPacket( Packet.EOF:new( ) )
+ self.tnssocket:close()
+ end,
+
+}
+
+-- copy paste of DB2Socket aka VNCSocket
+TNSSocket =
+{
+
+ new = function(self)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.Socket = nmap.new_socket()
+ -- We need this massive timeout due to Oracle 11g throttling of
+ -- repeated login attempts.
+ o.Socket:set_timeout(30000)
+ 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 )
+ -- Attempt to catch this as early as possible
+ if ( not(HAVE_SSL) ) then
+ return false, "This module requires OpenSSL"
+ end
+ 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,
+}
\ No newline at end of file
diff --git a/scripts/oracle-brute.nse b/scripts/oracle-brute.nse
new file mode 100644
index 000000000..6933fb68d
--- /dev/null
+++ b/scripts/oracle-brute.nse
@@ -0,0 +1,137 @@
+description = [[
+Performs password guessing against Oracle
+]]
+
+---
+-- @usage
+-- nmap --script oracle-brute -p 1521 --script-args oracle-brute.sid=ORCL
+--
+-- @output
+-- PORT STATE SERVICE REASON
+-- 1521/tcp open oracle syn-ack
+-- | oracle-brute:
+-- | Accounts
+-- | system:powell => Account locked
+-- | haxxor:haxxor => Login correct
+-- | Statistics
+-- |_ Perfomed 157 guesses in 8 seconds, average tps: 19
+--
+-- Summary
+-- -------
+-- x The Driver class contains the driver implementation used by the brute
+-- library
+--
+-- @args oracle-brute.sid the instance against which to perform password
+-- guessing
+--
+
+--
+-- Version 0.2
+-- Created 07/12/2010 - v0.1 - created by Patrik Karlsson
+-- Revised 07/23/2010 - v0.2 - added script usage and output and
+-- - oracle-brute.sid argument
+
+author = "Patrik Karlsson"
+license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
+categories = {"intrusive", "auth"}
+
+require 'shortport'
+require 'brute'
+require 'tns'
+
+portrule = shortport.port_or_service(1521, "oracle-tns", "tcp", "open")
+
+Driver =
+{
+
+ new = function(self, host, port)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.host = host
+ o.port = port
+ return o
+ end,
+
+ --- Connects performs protocol negotiation
+ --
+ -- @return true on success, false on failure
+ connect = function( self )
+ local status, data
+ self.helper = tns.Helper:new( self.host, self.port, nmap.registry.args['oracle-brute.sid'] )
+
+ status, data = self.helper:Connect()
+ if ( not(status) ) then
+ return status, data
+ end
+
+ return true
+ end,
+
+ --- Attempts to login to the Oracle server
+ --
+ -- @param username string containing the login username
+ -- @param password string containing the login password
+ -- @return status, true on success, false on failure
+ -- @return brute.Error object on failure
+ -- brute.Account object on success
+ login = function( self, username, password )
+ local status, data = self.helper:Login( username, password )
+
+ if ( status ) then
+ return true, brute.Account:new(username, password, "OPEN")
+ -- Check for account locked message
+ elseif ( data:match("ORA[-]28000") ) then
+ return true, brute.Account:new(username, password, "LOCKED")
+ -- check for any other message
+ elseif ( data:match("ORA[-]%d+")) then
+ stdnse.print_debug(3, "username: %s, password: %s, error: %s", username, password, data )
+ return false, brute.Error:new(data)
+ -- any other errors are likely communication related, attempt to re-try
+ else
+ local err = brute.Error:new(data)
+ err:setRetry(true)
+ return false, err
+ end
+
+ return false, brute.Error:new( data )
+
+ end,
+
+ --- Disconnects and terminates the Oracle TNS communication
+ disconnect = function( self )
+ self.helper:Close()
+ end,
+
+ --- Perform a connection with the helper, this makes sure that the Oracle
+ -- instance is correct.
+ --
+ -- @return status true on success false on failure
+ -- @return err containing the error message on failure
+ check = function( self )
+ local helper = tns.Helper:new( self.host, self.port, nmap.registry.args['oracle-brute.sid'] )
+ local status, err = helper:Connect()
+
+ if( status ) then
+ helper:Close()
+ return true
+ end
+
+ return false, err
+ end,
+
+}
+
+
+action = function(host, port)
+ local status, result
+ local engine = brute.Engine:new(Driver, host, port )
+
+ if ( not( nmap.registry.args['oracle-brute.sid'] ) and not( nmap.registry.args['tns.sid'] ) ) then
+ return "ERROR: Oracle instance not set (see oracle-brute.sid or tns.sid)"
+ end
+
+ status, result = engine:start()
+
+ return result
+end
\ No newline at end of file
diff --git a/scripts/oracle-enum-users.nse b/scripts/oracle-enum-users.nse
new file mode 100644
index 000000000..6e6d7e327
--- /dev/null
+++ b/scripts/oracle-enum-users.nse
@@ -0,0 +1,162 @@
+description = [[
+Attempts to determine valid Oracle user names against unpatched Oracle 11g
+servers.
+
+This script does only work against Oracle 11g pre October 2009 Critical Patch
+Update (CPU).
+]]
+
+---
+-- @usage
+-- nmap --script oracle-enum-users --script-args oracle-enum-users.sid=ORCL,userdb=orausers.txt -p 1521-1560
+--
+-- If no userdb is supplied the default userlist is used
+--
+-- @output
+-- PORT STATE SERVICE REASON
+-- 1521/tcp open oracle syn-ack
+-- | oracle-enum-users:
+-- | haxxor is a valid user account
+-- | noob is a valid user account
+-- |_ patrik is a valid user account
+--
+-- The get_random_string function was stolen from Ron's smb code
+--
+-- @args oracle-enum-users.sid the instance against which to attempt user
+-- enumeration
+
+-- Version 0.3
+
+-- Created 12/07/2010 - v0.1 - created by Patrik Karlsson
+-- Revised 21/07/2010 - v0.2 - revised to work with patched systems
+-- Revised 21/07/2010 - v0.3 - removed references to smb in get_random_string
+
+author = "Patrik Karlsson"
+license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
+categories = {"intrusive", "auth"}
+
+require 'shortport'
+require 'tns'
+require 'unpwdb'
+
+portrule = shortport.port_or_service(1521, 'oracle-tns' )
+
+local function checkAccount( host, port, user )
+
+ local helper = tns.Helper:new( host, port, nmap.registry.args['oracle-enum-users.sid'] )
+ local status, data = helper:Connect()
+ local tnscomm, auth
+ local auth_options = tns.AuthOptions:new()
+
+
+ if ( not(status) ) then
+ return false, data
+ end
+
+ -- A bit ugly, the helper should probably provide a getSocket function
+ tnscomm = tns.Comm:new( helper.tnssocket )
+
+ status, auth = tnscomm:exchTNSPacket( tns.Packet.PreAuth:new( user, auth_options ) )
+ if ( not(status) ) then
+ return false, auth
+ end
+ helper:Close()
+
+ return true, auth["AUTH_VFR_DATA"]
+end
+
+---Generates a random string of the requested length. This can be used to check how hosts react to
+-- weird username/password combinations.
+--@param length (optional) The length of the string to return. Default: 8.
+--@param set (optional) The set of letters to choose from. Default: upper, lower, numbers, and underscore.
+--@return The random string.
+local function get_random_string(length, set)
+ if(length == nil) then
+ length = 8
+ end
+
+ if(set == nil) then
+ set = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"
+ end
+
+ local str = ""
+
+ -- Seed the random number, if we haven't already
+ if (not(nmap.registry.oracle_enum_users) or not(nmap.registry.oracle_enum_users.seeded)) then
+ math.randomseed(os.time())
+ nmap.registry.oracle_enum_users = {}
+ nmap.registry.oracle_enum_users.seeded = true
+ end
+
+ for i = 1, length, 1 do
+ local random = math.random(#set)
+ str = str .. string.sub(set, random, random)
+ end
+
+ return str
+end
+
+
+
+action = function( host, port )
+
+ local known_good_accounts = { "system", "sys", "dbsnmp", "scott" }
+
+ local status, salt
+ local count = 0
+ local result = {}
+ local usernames
+
+ if ( not( nmap.registry.args['oracle-enum-users.sid'] ) and not( nmap.registry.args['tns.sid'] ) ) then
+ return "ERROR: Oracle instance not set (see oracle-brute.sid or tns.sid)"
+ end
+
+ status, usernames = unpwdb.usernames()
+ if( not(status) ) then
+ return stdnse.format_output(true, "ERROR: Failed to load the usernames dictionary")
+ end
+
+ -- Check for some known good accounts
+ for _, user in ipairs( known_good_accounts ) do
+ status, salt = checkAccount(host, port, user)
+ if( not(status) ) then return salt end
+ if ( salt ) then
+ count = count + #salt
+ end
+ end
+
+ -- did we atleast get a single salt back?
+ if ( count < 20 ) then
+ return stdnse.format_output(true, "ERROR: None of the known accounts were detected (oracle < 11g)")
+ end
+
+ -- Check for some known bad accounts
+ count = 0
+ for i=1, 10 do
+ local user = get_random_string(10)
+ status, salt = checkAccount(host, port, user)
+ if( not(status) ) then return salt end
+ if ( salt ) then
+ count = count + #salt
+ end
+ end
+
+ -- It's unlikely that we hit 3 random combinations as valid users
+ if ( count > 60 ) then
+ return stdnse.format_output(true, ("ERROR: %d of %d random accounts were detected (Patched Oracle 11G or Oracle 11G R2)"):format(count/20, 10))
+ end
+
+ for user in usernames do
+ status, salt = checkAccount(host, port, user)
+ if ( not(status) ) then return salt end
+ if ( salt and #salt == 20 ) then
+ table.insert( result, ("%s is a valid user account"):format(user))
+ end
+ end
+
+ if ( #result == 0 ) then
+ table.insert( result, "Failed to find any valid user accounts")
+ end
+
+ return stdnse.format_output(true, result)
+end
\ No newline at end of file
diff --git a/scripts/script.db b/scripts/script.db
index 11a402398..636f5d33c 100644
--- a/scripts/script.db
+++ b/scripts/script.db
@@ -87,6 +87,8 @@ Entry { filename = "nfs-showmount.nse", categories = { "discovery", "safe", } }
Entry { filename = "nfs-statfs.nse", categories = { "discovery", "safe", } }
Entry { filename = "ntp-info.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "ntp-monlist.nse", categories = { "discovery", "intrusive", } }
+Entry { filename = "oracle-brute.nse", categories = { "auth", "intrusive", } }
+Entry { filename = "oracle-enum-users.nse", categories = { "auth", "intrusive", } }
Entry { filename = "oracle-sid-brute.nse", categories = { "auth", "intrusive", } }
Entry { filename = "p2p-conficker.nse", categories = { "default", "safe", } }
Entry { filename = "pgsql-brute.nse", categories = { "auth", "intrusive", } }