Reproducing The ProxyShell Pwn2Own Exploit
INTRO
I and Jang recently successfully reproduced the ProxyShell Pwn2Own Exploit of Orange Tsai đ. Firstly, I just want to tell that I respect your hard work and the contribution of you to cybersecurity which inspired me many years ago. Now I want to summary the progress when we reproduce this Exploit chain as a write-up for our-self. When ZDI release the advisories about these bug, I decided to analysis this chain for learning purpose. We can almost finished the chain before the BlackHat US talkâs of Orange and then we found an missing piece when Orangeâs talk finished. So this write-up as an progress of 1-day analysis, we diff the patch, and solved the puzzle step by step. For those who never seen Orangeâs talk, you can check it here.

With these advisories, we can imagine that this chain maybe similar to ProxyLogon chain
- pre-auth SSRF
- somehow we can SSRF to /powershell endpoint
- finally calling cmdlets for post-auth RCE
ProxyLogon revisited
For each front-end endpoint like /ecp/, /owa/, /autodiscover/ , /powershell/ and so on, thereâs a class implement Microsoft.Exchange.HttpProxy.ProxyRequestHandler . We already know that from ProxyLogon analysis

From ProxyLogon, we know that we can set AnchoredRoutingTarget variable from âX-BEResourceâ, then Exchange when calculate the target backend URL to request internal we can reach internal endpoint we overwrite it and we have SSRF

Autodiscover Pre-auth SSRF
With the information from ZDI, pre-auth SSRF come from autodiscover service so we find some class which implement âMicrosoft.Exchange.HttpProxy.ProxyRequestHandlerâ and allow for unauthenticated

AutodiscoverProxyRequestHandler
=> implement EwsAutodiscoverProxyRequestHandler
=> implement BEServerCookieProxyRequestHandler
=> implement ProxyRequestHandler
And â/autodiscoverâ also allow for unauthenticated

With these information, we sure that this is what need need to focus on. Look back at the point we SSRF came from.

After ProxyLogon patch, thereâs a check for AnchoredRoutingTarget variable, so we somehow can successfully change it again like ProxyLogon, we will got 503 , donât know why? check here
ProxyRequestHandler.GetTargetBackEndServerUrl() will return the URI after finish calculate, we cannot abuse AnchoredRoutingTarget anymore, how about GetClientUrlForProxy() ? Then control our URI and send into backend, sound interesting

This is what we need to looking for, if our request IsAutodiscoverV2Request(), it will remove the âexplicitLogonAddressâ from URI and rebuild the URI.

How can we set explicitLogonAddress variable from our request?

Notice that, Params variable contains parameters from query string, form parameter, cookies, âŠ

We need to pass some conditions
- We want to reach the if statement so IsAutodiscoverV2Request() must return False and IsAutodiscoverV2PreviewRequest() return False also

Because IsAutodiscoverV2PreviewRequest() check EndsWith(â/autodiscover.jsonâ) and the path variable is AbsoluteUri we can make it return False like

2. explicitLogonAddress must contains valid email address
So it is â/autodiscover/autodiscover.json?a=dummy@dummy.pwâ (in order to help us can reach the if statement which will return False and remove explicitLogon ) and then we set this value into Email Cookie with the same value
3. When preparing request to send to backend internal, Exchange will generate Kerberos auth header and attach into Authorization header. This is why we can reach some other endpoint without any authentication

Chaining into together we have an pre-auth SSRF

Pre-auth SSRF into /powershell
The next bug we need to looking for is SSRF into â/powershell endpointâ

Always remember one think, you should understand on what you are looking while doing 1-day anlysis. IIS has some modules on each web application, they are excuted before the actual handler executed. You can imagine theyâre like âfilterâ mechanism on Java web apps.

We need to look at each module to see what we have missed. On BackendRehydrationModule when process the request, this module cannot get CommonAccessToken (from Exchange SSRF) there will be an exception and we cannot go through.


So how can we set the header âX-CommonAccessTokenâ because we cannot make Exchange copy it to SSRF request and send to â/powershellâ

Before BackendRehydrationModule executed, thereâs RemotePowershellBackendCmdletProxyModule

Basically, when the SSRF doesnât contain Header âX-CommonAccessTokenâ Exchange try to fetch the value of param âX-Rps-CATâ and then deserialize our controlled data to create an valid CommonAccessToken. Then at BackendRehydrationModule we will survive from the Exception. But how can we create a valid CommonAccessToken or maybe high privilege CommonAccessToken? We need to reverse the structure of CommonAccessToken

V + version + T + type + C + compress + data
if compress => decompress then if type is Windows
This is pseudocode I make for CommonAccessToken, if the token type is âWindowsâ, Exchange continue deserialise our data

A + authenType + L + logonName + U + user SUID
read group SUIDs
G + groupLength + SUIDs of group
At this point, I setup socat, change internal Exchange port from 444 to 4443, using socat listening on port 444, then redirect to BurpSuite port (8080) and finally foward into 4443. With this setup, we can capture a âsampleâ CommonAccessToken format and then crafting a suitable one.
How can you get a valid user SUID without exist user on Exchange? When deploying Exchange, there are some âalways existâ mailbox such as

Like ProxyLogon we can easily got SUID of this user âSystemMailbox{bb558c35â97f1â4cb9â8ff7-d53741dc928c}â with this flow
- SSRF to â/autodiscover/autodiscover.xmlâ
- Leaking user SUID via â/mapi/emsmdbâ
Now we got SUID, but how about group SUIDs? Check out this cmdlet
Get-Group | Format-List Identity,Sid
Now, we can craft an admin privilege CommonAccessToken via âX-Rps-CATâ parameter.
New-MailboxExportRequest arbitrary file write
From MS docs, this cmdlet can export the mailbox into arbitrary location. This mean we can write our shell into web root of Exchange and archive RCE?

We can confirm it again, because the patch only allow some specific extension

But how can we control the data in the mailbox and make it into shell after the file was exported? This is what we got stuck for a long time until Orangeâs talk appear.

Looking back into MS documents, Jang found this one which help us successfully write shell

But how can we put our shell into the mailbox and then export it as our shell?
EWS will save us, EWS (/ews/exchange.asmx) is a service based on SOAP which help us can create mail, event, meeting, âŠ
We can create an email saved in âdraftsâ for any user via SOAP header âSerializedSecurityContextâ- this called EWS Impersonation . Then injecting our âencodedâ shell as an attachment.

Channing all together
Now, we have every thing for this chain, the only thing we need to do is implement an WinRM protocol for the Pre-auth SSRF to comunicate with â/powershellâ endpoint. I leave this as an lesson for reader and hopefully you should reproduce this bug by yourself because it help you learn many things.
For myself, I use pypsrp then collect the data while it processing and plug it into our SSRF. To understand more about WinRM you can check this awesome blog
Or you can do the same with Orangeâs way, implement his own proxy to communicate with WinRM
Our demonstration:
https://www.youtube.com/watch?v=LbIYPFrltdA
Thanks everyone for reading to the end. Hopefully everyone stay safe during the Covid-19 pandemic recently at VietNam . Have a nice weekend !