##
# 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::Exploit::ORACLE

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Oracle Database Enumeration',
      'Description'    => %q{
        This module provides a simple way to scan an Oracle database server
        for configuration parameters that may be useful during a penetration
        test. Valid database credentials must be provided for this module to
        run.
      },
      'Author'         => [ 'Carlos Perez <carlos_perez[at]darkoperator.com>' ],
      'License'        => MSF_LICENSE
    ))

  end

  def run
    return if not check_dependencies

    begin
      # Get all values from v$parameter
      query = 'select name,value from v$parameter'
      vparm = {}
      params = prepare_exec(query)
      params.each do |l|
        name,value = l.split(",")
        vparm["#{name}"] = value
      end
    rescue => e
      if e.to_s =~ /ORA-00942: table or view does not exist/
        print_error("It appears you do not have sufficient rights to perform the check")
      else
        raise e
      end
    end

    print_status("Running Oracle Enumeration....")

    # Version Check
    query =  'select * from v$version'
    ver = prepare_exec(query)
    print_status("The versions of the Components are:")
    ver.each do |v|
      print_status("\t#{v.chomp}")
      report_note(
        :host => datastore['RHOST'],
        :proto => 'tcp',
        :sname => 'oracle',
        :port => datastore['RPORT'],
        :type => 'ORA_ENUM',
        :data => "Component Version: #{v.chomp}",
        :update => :unique_data
      )
    end

    # Saving Major Release Number for other checks
    majorrel = ver[0].scan(/Edition Release (\d*)./)

    #-------------------------------------------------------
    # Audit Check
    print_status("Auditing:")
    begin
      if vparm["audit_trail"] == "NONE"
        print_status("\tDatabase Auditing is not enabled!")
        report_note(
          :host => datastore['RHOST'],
          :proto => 'tcp',
          :sname => 'oracle',
          :port => datastore['RPORT'],
          :type => 'ORA_ENUM',
          :data => "Audit Trail: Disabled",
          :update => :unique_data
        )
      else
        print_status("\tDatabase Auditing is enabled!")
        report_note(
          :host => datastore['RHOST'],
          :proto => 'tcp',
          :sname => 'oracle',
          :port => datastore['RPORT'],
          :type => 'ORA_ENUM',
          :data => "Audit Trail: Enabled",
          :update => :unique_data
        )
      end

      if vparm["audit_sys_operations"] == "FALSE"
        print_status("\tAuditing of SYS Operations is not enabled!")
        report_note(
          :host => datastore['RHOST'],
          :proto => 'tcp',
          :sname => 'oracle',
          :port => datastore['RPORT'],
          :type => 'ORA_ENUM',
          :data => "Audit SYS Ops: Disabled",
          :update => :unique_data
        )
      else
        print_status("\tAuditing of SYS Operations is enabled!")
        report_note(
          :host => datastore['RHOST'],
          :proto => 'tcp',
          :sname => 'oracle',
          :port => datastore['RPORT'],
          :type => 'ORA_ENUM',
          :data => "Audit SYS Ops: Enabled",
          :update => :unique_data
        )
      end

    end

    #-------------------------------------------------------
    # Security Settings
    print_status("Security Settings:")
    begin

      if vparm["sql92_security"] == "FALSE"
        print_status("\tSQL92 Security restriction on SELECT is not Enabled")
        report_note(
          :host => datastore['RHOST'],
          :proto => 'tcp',
          :sname => 'oracle',
          :port => datastore['RPORT'],
          :type => 'ORA_ENUM',
          :data => "SQL92: Disabled",
          :update => :unique_data
        )
      else
        print_status("\tSQL92 Security restriction on SELECT is Enabled")
        report_note(
          :host => datastore['RHOST'],
          :proto => 'tcp',
          :sname => 'oracle',
          :port => datastore['RPORT'],
          :type => 'ORA_ENUM',
          :data => "SQL92: Enabled",
          :update => :unique_data
        )
      end

      # check for encryption of logins on version before 10g

      if majorrel.join.to_i < 10
        if vparm["dblink_encrypt_login"] == "FALSE"
          print_status("\tLink Encryption for Logins is not Enabled")
          report_note(
            :host => datastore['RHOST'],
            :proto => 'tcp',
            :sname => 'oracle',
            :port => datastore['RPORT'],
            :type => 'ORA_ENUM',
            :data => "Link Encryption: Disabled",
            :update => :unique_data
          )
        else
          print_status("\tLink Encryption for Logins is Enabled")
          report_note(
            :host => datastore['RHOST'],
            :proto => 'tcp',
            :sname => 'oracle',
            :port => datastore['RPORT'],
            :type => 'ORA_ENUM',
            :data => "Link Encryption: Enabled",
            :update => :unique_data
          )
        end
      end

      print_status("\tUTL Directory Access is set to #{vparm["utl_file_dir"]}") if vparm["utl_file_dir"] != " "
      report_note(
        :host => datastore['RHOST'],
        :proto => 'tcp',
        :sname => 'oracle',
        :port => datastore['RPORT'],
        :type => 'ORA_ENUM',
        :data => "UTL_DIR: #{ vparm["utl_file_dir"]}"
      ) if not vparm["utl_file_dir"]#.empty?

      print_status("\tAudit log is saved at #{vparm["audit_file_dest"]}")
      report_note(
        :host => datastore['RHOST'],
        :proto => 'tcp',
        :sname => 'oracle',
        :port => datastore['RPORT'],
        :type => 'ORA_ENUM',
        :data => "Audit Log Location: #{ vparm["audit_file_dest"]}"
      ) if not vparm["audit_file_dest"]#.empty?

    end

    #-------------------------------------------------------
    # Password Policy
    print_status("Password Policy:")
    begin
      query = %Q|
        SELECT limit
        FROM dba_profiles
        WHERE resource_name = 'PASSWORD_LOCK_TIME'
        AND profile         = 'DEFAULT'
      |
      lockout = prepare_exec(query)
      print_status("\tCurrent Account Lockout Time is set to #{lockout[0].chomp}")
      report_note(
        :host => datastore['RHOST'],
        :proto => 'tcp',
        :sname => 'oracle',
        :port => datastore['RPORT'],
        :type => 'ORA_ENUM',
        :data => "Account Lockout Time: #{lockout[0].chomp}",
        :update => :unique_data
      )

    rescue => e
      if e.to_s =~ /ORA-00942: table or view does not exist/
        print_error("It appears you do not have sufficient rights to perform the check")
      else
        raise e
      end
    end

    begin
      query = %Q|
        SELECT limit
        FROM dba_profiles
        WHERE resource_name = 'FAILED_LOGIN_ATTEMPTS'
        AND profile         = 'DEFAULT'
      |
      failed_logins = prepare_exec(query)
      print_status("\tThe Number of Failed Logins before an account is locked is set to #{failed_logins[0].chomp}")
      report_note(
        :host => datastore['RHOST'],
        :proto => 'tcp',
        :sname => 'oracle',
        :port => datastore['RPORT'],
        :type => 'ORA_ENUM',
        :data => "Account Fail Logins Permitted: #{failed_logins[0].chomp}",
        :update => :unique_data
      )

    rescue => e
      if e.to_s =~ /ORA-00942: table or view does not exist/
        print_error("It appears you do not have sufficient rights to perform the check")
      else
        raise e
      end
    end

    begin
      query = %Q|
        SELECT limit
        FROM dba_profiles
        WHERE resource_name = 'PASSWORD_GRACE_TIME'
        AND profile         = 'DEFAULT'
      |
      grace_time = prepare_exec(query)
      print_status("\tThe Password Grace Time is set to #{grace_time[0].chomp}")
      report_note(
        :host => datastore['RHOST'],
        :proto => 'tcp',
        :sname => 'oracle',
        :port => datastore['RPORT'],
        :type => 'ORA_ENUM',
        :data => "Account Password Grace Time: #{grace_time[0].chomp}",
        :update => :unique_data
      )

    rescue => e
      if e.to_s =~ /ORA-00942: table or view does not exist/
        print_error("It appears you do not have sufficient rights to perform the check")
      else
        raise e
      end
    end

    begin
      query = %Q|
        SELECT limit
        FROM dba_profiles
        WHERE resource_name = 'PASSWORD_LIFE_TIME'
        AND profile         = 'DEFAULT'
      |
      passlife_time = prepare_exec(query)
      print_status("\tThe Lifetime of Passwords is set to #{passlife_time[0].chomp}")
      report_note(
        :host => datastore['RHOST'],
        :proto => 'tcp',
        :sname => 'oracle',
        :port => datastore['RPORT'],
        :type => 'ORA_ENUM',
        :data => "Password Life Time: #{passlife_time[0].chomp}",
        :update => :unique_data
      )

    rescue => e
      if e.to_s =~ /ORA-00942: table or view does not exist/
        print_error("It appears you do not have sufficient rights to perform the check")
      else
        raise e
      end
    end

    begin
      query = %Q|
        SELECT limit
        FROM dba_profiles
        WHERE resource_name = 'PASSWORD_REUSE_TIME'
        AND profile         = 'DEFAULT'
      |
      passreuse = prepare_exec(query)
      print_status("\tThe Number of Times a Password can be reused is set to #{passreuse[0].chomp}")
      report_note(
        :host => datastore['RHOST'],
        :proto => 'tcp',
        :sname => 'oracle',
        :port => datastore['RPORT'],
        :type => 'ORA_ENUM',
        :data => "Password Reuse Time: #{passreuse[0].chomp}",
        :update => :unique_data
      )

    rescue => e
      if e.to_s =~ /ORA-00942: table or view does not exist/
        print_error("It appears you do not have sufficient rights to perform the check")
      else
        raise e
      end
    end

    begin
      query = %Q|
        SELECT limit
        FROM dba_profiles
        WHERE resource_name = 'PASSWORD_REUSE_MAX'
        AND profile         = 'DEFAULT'
      |
      passreusemax = prepare_exec(query)
      print_status("\tThe Maximum Number of Times a Password needs to be changed before it can be reused is set to #{passreusemax[0].chomp}")
      report_note(
        :host => datastore['RHOST'],
        :proto => 'tcp',
        :sname => 'oracle',
        :port => datastore['RPORT'],
        :type => 'ORA_ENUM',
        :data => "Password Maximun Reuse Time: #{passreusemax[0].chomp}",
        :update => :unique_data
      )
      print_status("\tThe Number of Times a Password can be reused is set to #{passreuse[0].chomp}")

    rescue => e
      if e.to_s =~ /ORA-00942: table or view does not exist/
        print_error("It appears you do not have sufficient rights to perform the check")
      else
        raise e
      end
    end

    begin
      query = %Q|
        SELECT limit
        FROM dba_profiles
        WHERE resource_name = 'PASSWORD_VERIFY_FUNCTION'
        AND profile         = 'DEFAULT'
      |
      passrand = prepare_exec(query)
      if passrand[0] =~ /NULL/
        print_status("\tPassword Complexity is not checked")
        report_note(
          :host => datastore['RHOST'],
          :proto => 'tcp',
          :sname => 'oracle',
          :port => datastore['RPORT'],
          :type => 'ORA_ENUM',
          :data => "Password Complexity is not being checked for new passwords",
          :update => :unique_data
        )
      else
        print_status("\tPassword Complexity is being checked")
        report_note(
          :host => datastore['RHOST'],
          :proto => 'tcp',
          :sname => 'oracle',
          :port => datastore['RPORT'],
          :type => 'ORA_ENUM',
          :data => "Password Complexity is being checked for new passwords",
          :update => :unique_data
        )
      end

    rescue => e
      if e.to_s =~ /ORA-00942: table or view does not exist/
        print_error("It appears you do not have sufficient rights to perform the check")
      else
        raise e
      end
    end

    #-------------------------------------------------------

    begin

      if majorrel.join.to_i < 11

        query = %Q|
          SELECT name, password
          FROM sys.user$
          where password != 'null' and  type# = 1 and astatus = 0
        |
        activeacc = prepare_exec(query)
        print_status("Active Accounts on the System in format Username,Hash are:")
        activeacc.each do |aa|
          print_status("\t#{aa.chomp}")
          report_note(
            :host => datastore['RHOST'],
            :proto => 'tcp',
            :sname => 'oracle',
            :port => datastore['RPORT'],
            :type => 'ORA_ENUM',
            :data => "Active Account #{aa.chomp}",
            :update => :unique_data
          )
        end
      else
        query = %Q|
          SELECT name, password, spare4
          FROM sys.user$
          where password != 'null' and  type# = 1 and astatus = 0
        |
        activeacc = prepare_exec(query)
        print_status("Active Accounts on the System in format Username,Password,Spare4 are:")
        activeacc.each do |aa|
          print_status("\t#{aa.chomp}")
          report_note(
            :host => datastore['RHOST'],
            :proto => 'tcp',
            :sname => 'oracle',
            :port => datastore['RPORT'],
            :type => 'ORA_ENUM',
            :data => "Active Account #{aa.chomp}",
            :update => :unique_data
          )
        end
      end

    rescue => e
      if e.to_s =~ /ORA-00942: table or view does not exist/
        print_error("It appears you do not have sufficient rights to perform the check")
      else
        raise e
      end
    end

    begin
      if majorrel.join.to_i < 11
        query = %Q|
          SELECT username, password
          FROM dba_users
          WHERE account_status = 'EXPIRED & LOCKED'
        |
        disabledacc = prepare_exec(query)
        print_status("Expired or Locked Accounts on the System in format Username,Hash are:")
        disabledacc.each do |da|
          print_status("\t#{da.chomp}")
          report_note(
            :host => datastore['RHOST'],
            :proto => 'tcp',
            :sname => 'oracle',
            :port => datastore['RPORT'],
            :type => 'ORA_ENUM',
            :data => "Disabled Account #{da.chomp}",
            :update => :unique_data
          )
        end
      else
        query = %Q|
          SELECT name, password, spare4
          FROM sys.user$
          where password != 'null' and  type# = 1 and astatus = 8 or astatus = 9
        |
        disabledacc = prepare_exec(query)
        print_status("Expired or Locked Accounts on the System in format Username,Password,Spare4 are:")
        disabledacc.each do |da|
          print_status("\t#{da.chomp}")
          report_note(
            :host => datastore['RHOST'],
            :proto => 'tcp',
            :sname => 'oracle',
            :port => datastore['RPORT'],
            :type => 'ORA_ENUM',
            :data => "Disabled Account #{da.chomp}",
            :update => :unique_data
          )
        end
      end

    rescue => e
      if e.to_s =~ /ORA-00942: table or view does not exist/
        print_error("It appears you do not have sufficient rights to perform the check")
      else
        raise e
      end
    end

    begin
      query = %Q|
        SELECT grantee
        FROM dba_role_privs
        WHERE granted_role = 'DBA'
      |
      dbaacc = prepare_exec(query)
      print_status("Accounts with DBA Privilege  in format Username,Hash on the System are:")
      dbaacc.each do |dba|
        print_status("\t#{dba.chomp}")
        report_note(
          :host => datastore['RHOST'],
          :proto => 'tcp',
          :sname => 'oracle',
          :port => datastore['RPORT'],
          :type => 'ORA_ENUM',
          :data => "Account with DBA Priv  #{dba.chomp}",
          :update => :unique_data
        )
      end

    rescue => e
      if e.to_s =~ /ORA-00942: table or view does not exist/
        print_error("It appears you do not have sufficient rights to perform the check")
      else
        raise e
      end
    end

    begin
      query = %Q|
        SELECT grantee
        FROM dba_sys_privs
        WHERE privilege = 'ALTER SYSTEM'
      |
      altersys = prepare_exec(query)
      print_status("Accounts with Alter System Privilege on the System are:")
      altersys.each do |as|
        print_status("\t#{as.chomp}")
        report_note(
          :host => datastore['RHOST'],
          :proto => 'tcp',
          :sname => 'oracle',
          :port => datastore['RPORT'],
          :type => 'ORA_ENUM',
          :data => "Account with ALTER SYSTEM Priv  #{as.chomp}",
          :update => :unique_data)
      end

    rescue => e
      if e.to_s =~ /ORA-00942: table or view does not exist/
        print_error("It appears you do not have sufficient rights to perform the check")
      else
        raise e
      end
    end

    begin
      query = %Q|
        SELECT grantee
        FROM dba_sys_privs
        WHERE privilege = 'JAVA ADMIN'
      |
      javaacc = prepare_exec(query)
      print_status("Accounts with JAVA ADMIN Privilege on the System are:")
      javaacc.each do |j|
        print_status("\t#{j.chomp}")
        report_note(
          :host => datastore['RHOST'],
          :proto => 'tcp',
          :sname => 'oracle',
          :port => datastore['RPORT'],
          :type => 'ORA_ENUM',
          :data => "Account with JAVA ADMIN Priv  #{j.chomp}",
          :update => :unique_data
        )
      end

    rescue => e
      if e.to_s =~ /ORA-00942: table or view does not exist/
        print_error("It appears you do not have sufficient rights to perform the check")
      else
        raise e
      end
    end

    begin
      query = %Q|
        select grantee
        from dba_sys_privs
        where privilege = 'CREATE LIBRARY'
        or privilege = 'CREATE ANY'
      |
      libpriv = prepare_exec(query)
      print_status("Accounts that have CREATE LIBRARY Privilege on the System are:")
      libpriv.each do |lp|
        print_status("\t#{lp.chomp}")
        report_note(
          :host => datastore['RHOST'],
          :proto => 'tcp',
          :sname => 'oracle',
          :port => datastore['RPORT'],
          :type => 'ORA_ENUM',
          :data => "Account with CREATE LIBRARY Priv  #{lp.chomp}",
          :update => :unique_data
        )
      end

    rescue => e
      if e.to_s =~ /ORA-00942: table or view does not exist/
        print_error("It appears you do not have sufficient rights to perform the check")
      else
        raise e
      end
    end

    #Default Password Check
    begin
      print_status("Default password check:")
      if majorrel.join.to_i == 11
        query = %Q|
          SELECT * FROM dba_users_with_defpwd
        |
        defpwd = prepare_exec(query)
        defpwd.each do |dp|
          print_status("\tThe account #{dp.chomp} has a default password.")
          report_note(
            :host => datastore['RHOST'],
            :proto => 'tcp',
            :sname => 'oracle',
            :port => datastore['RPORT'],
            :type => 'ORA_ENUM',
            :data => "Account with Default Password #{dp.chomp}",
            :update => :unique_data
          )
        end

      else
        query = %Q|
          SELECT name, password
          FROM sys.user$
          where password != 'null' and  type# = 1
        |
        ordfltpss = "#{File.join(Msf::Config.data_directory, "wordlists", "oracle_default_hashes.txt")}"
        returnedstring = prepare_exec(query)
        accts = {}
        returnedstring.each do |record|
          user,pass = record.split(",")
          accts["#{pass.chomp}"] = user
        end
        ::File.open(ordfltpss, "rb").each_line do  |l|
          accrcrd =  l.split(",")
          if accts.has_key?(accrcrd[2])
            print_status("\tDefault pass for account #{accrcrd[0]} is #{accrcrd[1]} ")
            report_note(
              :host => datastore['RHOST'],
              :proto => 'tcp',
              :sname => 'oracle',
              :port => datastore['RPORT'],
              :type => 'ORA_ENUM',
              :data => "Account with Default Password #{accrcrd[0]} is #{accrcrd[1]}",
              :update => :unique_data
            )
          end
        end
      end
    rescue => e
      if e.to_s =~ /ORA-00942: table or view does not exist/
        print_error("It appears you do not have sufficient rights to perform the check")
      else
        raise e
      end
    end
  end
end
