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 !