#!/usr/bin/env ruby


require 'msf/core'
require 'msf/core/exploit/oracle'

class Metasploit3 < Msf::Auxiliary

	include Msf::Exploit::ORACLE
	include Msf::Exploit::FILEFORMAT
	
	def initialize(info = {})
		super(update_info(info,
			'Name'           => 'Oracle Enumerator',
			'Description'    => %q{
					This module seeks to get security relevant information from an Oracle Database.
					Intended to be useful post exploitation, when DBA creds are available.
			},
			'Author'         => [ 'rorym@nmrconsult.net' ],
			'License'        => MSF_LICENSE,
			'Version'        => '$Revision:$',
			'References'     =>
				[
					[ 'URL', 'http://www.mccune.org.uk' ],
				],
			'DisclosureDate' => 'TBD'))

			register_options(
				[
					OptString.new('FILENAME', [ false, 'File to output results to','oracle_enum.txt'  ]),
					OptString.new('OUTPUTPATH', [ false, 'Directory for outputing results to',  './data/exploits/']),
				], self.class)
			
			
	end


	def run
		#This first section does the basic queries that don't require any analysis.  Then follow-on queries that require some analysis are broken out below.
                queries = [
				['Oracle User List','select username,account_status,password from dba_users'], 
				['Oracle Password Policy',"select PROFILE,RESOURCE_NAME,LIMIT from dba_profiles WHERE resource_type = 'PASSWORD'"],	
				['Oracle version Numbers', "select * from v$version"],
				['Oracle Patch Level',"select * from dba_registry_history"]		
			  ]

		output = ''

		output << "Output from Oracle Enumerator\n------------------\n\n"

		queries.each do |query|
			query_title = query[0]
			query_sql = query[1]
			print_status("query is " + query_sql)
		 	scan_test = connect.prepare(query_sql)
			scan_test.execute
			output << "#{query_title}\n-----------\n"
			output << "Executed query was : #{query_sql} \n-----------\n"
			scan_test.each do |result|
				output << "#{result.join(",").to_s}\n"
			end
			output << "\n============\n\n"
		end
		output << tab_privs_rights_scan
		output << audit_priv_profile_scan
		output << sys_privs_rights_scan
		output << vparameter_scan
		file_create(output)
		print_status("output is " + output.length.to_s + " characters long ")
	end

	def tab_privs_rights_scan
  		tab_privs_rights_scan_sql = 'select grantee,table_name,privilege from dba_tab_privs'
		result = ''
		result << "\nTable Access Rights Scan\n"
		result << "------------------------\n"
		result << "Executed query was : #{tab_privs_rights_scan_sql}\n"
		result << "----------------------\n"
		tab_privs_test = connect.prepare(tab_privs_rights_scan_sql)
		tab_rights_array = Array.new
		print_status("query is " + tab_privs_rights_scan_sql)
		tab_privs_test.execute
		tab_privs_test.each do |r|
			#This next line should remove any grants to the DBA role or the SYS user from the report as they already have full privilege to the system
			unless r[0] == 'SYS' || r[0] == 'DBA' || r[0] == 'SELECT_CATALOG_ROLE'
			#This is the list of 'sensitive' tables from the CIS standard
			#TODO: Need to find some way to clean up this line as it's really ugly
				if r[1] == 'USER$' || r[1] == 'AUD$' || r[1] == 'USER_HISTORY$' || r[1] == 'LINK$' || r[1] == 'SOURCE$' || r[1] == 'STATS$SQLTEXT' || r[1] == 'STATS$SQL_SUM' || r[1] =~ /^X\$/ || r[1] == 'DBA_ROLES'  || r[1] =~ /^V_\$/ || r[1] == 'ALL_SOURCE' || r[1] == 'DBA_SYS_PRIVS' || r[1] == 'DBA_TAB_PRIVS' || r[1] == 'DBA_ROLE_PRIVS' || r[1] == 'DBA_USERS' || r[1] == 'ROLE_ROLE_PRIVS' || r[1] == 'USER_TAB_PRIVS' || r[1] == 'USER_ROLE_PRIVS'

					tab_rights_array << [r[0], r[1], r[2]]
				end
			end
		end
		tab_rights_array.sort!

		tab_rights_array.each do |line|
			result << "#{line[0]}, #{line[1]},#{line[2]}\n"        
		end
	return result
	end

	def audit_priv_profile_scan
		audit_priv_profile_scan_sql = 'select * from dba_priv_audit_opts'
		result = ''
		result << "\nSystem Privilege Auditing Scan\n"
		result << "-------------------------\n"
		result << "Executed query was : #{audit_priv_profile_scan_sql}\n"
		result << "-------------------------\n"
		result << "Result Columns are Privilege, User, Success Audited?, Failure Audited?\n"
		print_status("query is " + audit_priv_profile_scan_sql)
		audit_privs_test = connect.prepare(audit_priv_profile_scan_sql)
		audit_privs_test.execute
		audit_priv_profile_scan_results = Array.new
		priv_audit_results = Hash.new
		#Retrieve the contents of the audit table
		audit_privs_test.each do |r|
			if !priv_audit_results[r[2]]
				priv_audit_results[r[2]] = Array.new
				priv_audit_results[r[2]] << [r[0], r[3], r[4]]
			else 
				priv_audit_results[r[2]] << [r[0], r[3], r[4]]
			end
		end

		privs_to_audit = ['CREATE SESSION', 'ALTER ANY TABLE', 'ALTER USER','CREATE ROLE', 'CREATE USER','DROP ANY PROCEDURE','DROP ANY TABLE','GRANT ANY PRIVILEGE','GRANT ANY ROLE']
  		#Compare against system privileges.  If the table is empty one result of "not set", "not set" will come back for each privilege
		privs_to_audit.each do |priv|
			if !priv_audit_results[priv]
				priv_audit_results[priv] = Array.new
				priv_audit_results[priv] << [' ','NOT SET','NOT SET']
			end

		end
  
  
		priv_audit_results.each do |key, value|
			value.each do |val|
				result << "#{key}, #{val[0]}, #{val[1]}, #{val[2]}\n"
			end
		end
	return result
	end
	
	def sys_privs_rights_scan
		sys_privs_rights_scan_sql = "select privilege,grantee from dba_sys_privs where privilege like '%ANY%' or privilege = 'EXEMPT ACCESS POLICY' "
		result = ''
		result << "\nSystem Privileges Scan\n"
		result << "----------------------\n"
		result << "Executed query was : #{sys_privs_rights_scan_sql}"
		result << "----------------------\n"
		access_rights_hash = Hash.new
		print_status("query is " + sys_privs_rights_scan_sql)
		sys_privs_test = connect.prepare(sys_privs_rights_scan_sql)
		sys_privs_test.execute
		sys_privs_test.each do |r|
			if !access_rights_hash[r[1]]
				access_rights_hash[r[1]] = r[0]
			else
				access_rights_hash[r[1]] = access_rights_hash[r[1]] + ',' + r[0]
			end
		end
  
		access_rights_hash.each do |key,value|
			result << "#{key}, #{value}\n"
		end
	return result
	end

	def vparameter_scan
	 	vparameter_sql = "select name,value from v$parameter"
	 	result = ''
		result << "\nv$parameter checks\n"
		result << "This returns potential security issues from v$parameter and a reference to the relevent CIS standard section\n"
		result << "--------------------\n"
		result << "Executed query was : #{vparameter_sql}\n"
		parameter_results = Hash.new
		print_status("query is " + vparameter_sql)
		vparameter_test = connect.prepare(vparameter_sql)
		vparameter_test.execute
		vparameter_test.each do |r|
			parameter_results[r[0]] = r[1]
		end
	
		#Ok now we have a hash of all the parameters from v$parameter its relatively 
		#straightforward to check against the desired values
	  
		if parameter_results['global_names'] != 'TRUE'
			result << "CIS 4.02, GLOBAL_NAMES, #{parameter_results['global_names']}\n"
		end

	
		if parameter_results['max_enabled_roles'].to_i > 30
			result << "CIS 4.03, MAX_ENABLED_ROLES, #{parameter_results['max_enabled_roles']}"
		end

		if parameter_results['remote_os_authent'] != 'FALSE'
			result << "CIS 4.04, REMOTE_OS_AUTHENT, #{parameter_results['remote_os_authent']}\n"
		end
	
		if parameter_results['remote_os_roles'] != 'FALSE'
			result << "CIS 4.05, REMOTE_OS_ROLES, #{parameter_results['remote_os_roles']}\n"
		end

		if parameter_results['remote_listener']
			result << "CIS 4.06, REMOTE_LISTENER, #{parameter_results['remote_listener']}\n"
		end


		if parameter_results['audit_trail'] !=~ /OS|DB|TRUE/
			result << "CIS 4.07, AUDIT_TRAIL, #{parameter_results['audit_trail']}\n"
		end
	  
		if parameter_results['os_authent_prefix']
			result << "CIS 4.08, OS_AUTHENT_PREFIX, #{parameter_results['os_authent_prefix']}\n"
		end
	  
		if parameter_results['os_roles'] != 'FALSE'
			result << "CIS 4.09, OS_ROLES, #{parameter_results['os_roles']}\n"
		end

		if parameter_results['utl_file_dir']
			result << "CIS 4.10, UTL_FILE_DIR, #{parameter_results['utl_file_dir']}\n"
		end
	  
		if parameter_results['sql92_security'] != 'TRUE'
			result << "CIS 4.13, SQL92_SECURITY, #{parameter_results['sql92_security']}\n"
		end
	  
		if parameter_results['o7_dictionary_accessibility'] != 'FALSE'
			result << "CIS 4.18, O7_DICTIONARY_ACCESSIBILITY, #{parameter_results['o7_dictionary_accessibility']}\n"
		end
	  
		if parameter_results['audit_sys_operations'] != 'TRUE'
			result << "CIS 4.20, AUDIT_SYS_OPERATIONS, #{parameter_results['audit_sys_operations']}\n"
		end
	  
	
		if parameter_results['remote_login_passwordfile'] 
			result << "CIS 4.28, REMOTE_LOGIN_PASSWORDFILE, #{parameter_results['remote_login_passwordfile']}\n"
		end
	return result
	end


end

