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