ProxyShell Microsoft Exchange
Reference:
- The original talk is from Orange Tsai: https://i.blackhat.com/USA21/Wednesday-Handouts/us-21-ProxyLogon-Is-Just-The-Tip-Of-The-Iceberg-A-New-Attack-Surface-On-Microsoft-Exchange-Server.pdf?fbclid=IwAR2V0-4k2yb8dmPP5Mksd8iHYTOfE6sBwygMt4wjq3M9be8Tw6TlH0andhA
- Amazing research write up from peterjson and Jang: https://peterjson.medium.com/reproducing-the-proxyshell-pwn2own-exploit-49743a4ea9a1
- Another amazing write up: https://y4y.space/2021/08/12/my-steps-of-reproducing-proxyshell/
1. Pre-auth SSRF
The endpoint /autodiscover.json is one of the endpoints that we can access without authentication
If our URL end with /autodiscover.json , ClientRequest will fetch the param Email
explicitLogonAddress must contains valid email address
So if our explicitLogonAddress=/autodiscover/autodiscover.json?a=a@test.com then the /autodiscover/autodiscover.json?a=a@test.com part will be removed from the URI.
ex:
1 | http://exchange.local/autodiscover/autodiscover.json@test.com/mapi/nspi?&Email=autodiscover/autodiscover.json%3F@test.com |
Will become
1 | http://exchange.local/mapi/nspi?&Email=autodiscover/autodiscover.json%3F@test.com |
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
The Fatal erase:
1 | GET /autodiscover/autodiscover.json?@test.com/mapi/nspi?&Email=autodiscover/autodiscover.json%3F@test.com HTTP/2 |
1 | HTTP/2 200 OK |
We archieved the Pre-auth SSRF, direct access to Exchange Server back-end !!!
2. Exchange Powershell Remoting
The Exchange PowerShell Remoting is built upon PowerShell API and uses the Runspace for isolations. All operations are based on WinRM protocol
We need to look for the way to access /powershell endpoint, by accessing /powershell endpoint, we are one-step closer to the final goal - RCE
From Orange Tsai’s talk, he said that because we access the endpoint with NT\SYSTEM priviledge, we will fail the business logic since SYSTEM does not have any mailbox.
We cannot forge the X-CommonAccessToken because it’s in the blacklisted cookies/headers
A few modules we should pay attention to
1 | Microsoft.Exchange.Security |
From the Orange Tsai’s talk, we know that the BackendRehydrationModule play an important part in authentication process
Microsoft.Exchange.Security.Authentication.BackendRehydrationModule
We cannot access /powershell endpoint because we don’t have X-CommonAccessToken header, we cannot forge the X-CommonAccessToken: <token> to impersonate other user because X-CommonAccessToken is in the blacklisted headers. So what to do ?
Lucky for us, we have a module is called before the BackendRehydrationModule and it extract Access-Token fromURL
Microsoft.Exchange.Configuration.RemotePowershellBackendCmdletProxyModule
The code’s logic look for X-CommonAccessToken header, if the header is not exist, it will extract X-RPS-CAT param and deserialize it as a Access Token (X-CommonAccessToken )
Microsoft.Exchange.Security.Authorization.CommonAccessToken ( serialization process)
Microsoft.Exchange.Security.Authorization.CommonAccessToken (deserialization process)
The pseudo code for the token deserialization:
1 | V + this.Version + T + this.TokenType C + compress + data |
Pseudo code for DeserializeFromToken
1 | A + this.AuthenticationType + L + this.LogonName + U + UserSID + G + Group Length + GroupSids |
Now, we can craft an admin privilege CommonAccessToken via “X-Rps-CAT” parameter since we know how the Token is constructed
We need a UserSID to craft our token
1 | def get_sid(url: str, email: str): |
I copy the gen_token function from this amazing write up to help me build the poc script/
1 | def gen_token(email: str, sid: str): |
Use the token to request to /powershell endpoint, if the server return with 200 status code, that means the token is accepted
So now, we can execute arbitrary Powershell code on the exchange server with Admin priviledge. But the Powershell Cmdlet module come with a very limited list of commands that we can execute. We want more than that !!!
3. Working with remote Powershell and archieved the post-auth RCE
Since we are now an admin of Exchange Server, there are many potential commands to abuse to get Post Auth RCE. I will use the New-MailboxExportRequest command
According to Microsoft docs, New-MailboxExportRequest allow us to export user’s mailbox to a file. That allow us to write arbitrary file to any location, we can write our shell to web root location of Exchange server.
This is where the arbitrary write file happens, the API doesn’t check that the exported files have to be a certain format extension, like .pst,..., so we can use that and export our payload to any file extension, like abc.aspx for example
ex:
1 | New-MailboxExportRequest -Mailbox AylaKol -FilePath "\\SERVER01\PSTFileShare\Ayla_Recovered.pst" |
The exported file is encoded and in PST format. Now come the fun part, how do we write the data to mailbox so that after the mail is exported into a PST file, it still a useable shell for us ?.
Follow Orange Tsai’s talk, he showed us how to encode the payload first and then send it to the Exchange Server, when the Exchange server try to save and export the file and encode it again, it will turns it into the orginal malicious code . This MS-doc will help us how to encode our shell before sending.
At the time I was writing this research, I haven’t found any way to implement this in python, so I copied the C++ code from Microsoft Blog and modify it a little bit
1 |
|
Got encodeded data in base64
Now we know how to encode our payload, how do we send mail to the Admin’s mailbox ?
The original talk from Orange Tsai, he delivered the payload through SMTP, but I like the Jang and PeterJson’s way more. That is EWS Impersonation
By sending request to /EWS/exchange.asmx . We can create an email and save it in Drafts for any user via SOAP header SerializedSecurityContext
That’s for the Post-Auth RCE part, for communicating with Remote Powershell, I follow the other researchers’s way. Use pypsrp, implement the proxy and forward requests to communicate with wsman
4. Chaining everything together - the ProxyShell
Now we have everything we need, let’s chain it together:
- Use the Pre-auth SSRF to generate the token
- Use the token to request to Remote Powershell server
- Send email contains the malicious payload to user
- Assign Mailbox Import/Export role to our current session
1 | New-ManagementRoleAssignment -Role "Mailbox Import Export" -User email@email |
- Export malicious email to webroot
- Enjoy the shell.
POC video
Conclusion
This is a very nice exploit chain. For me, it wasn’t easy to reproduce this at all, I have to read and research a lot, that was the most fun part and I learnt a lot. Orange Tsai is an amazing researcher and I’m a big fan of his work.
I still haven’t understood the whole exploit chain, especially the Permuative Encoding part, so if anyone knows, please contact me via Twitter and explain it to me. I will appreciate it a lot ;)