0x00 前言
Apache Guacamole是較為流行的一種遠程辦公基礎框架,在全球范圍內已有超過1000萬次的Docker下載。在研究過程中,我們發現Apache Guacamole存在多個嚴重的反向RDP漏洞,并且也受我們在FreeRDP中找到的一些新漏洞的影響。簡而言之,如果攻擊者成功入侵了某個組織的內部計算機,那么當正常用戶嘗試連接被控制的主機時,攻擊者就可以利用這些漏洞來攻擊Guacamole網關。惡意攻擊者可以完全控制guacamole-server,攔截并控制其他已連接的會話。
攻擊過程可參考該視頻,視頻中我們成功利用這些漏洞控制Guacamole以及已連接的所有會話。
0x01 Apache Guacamole簡介
隨著疫情期間遠程辦公的興起,我們認為針對遠程辦公的技術方案將變成一個熱點的研究目標,因此選擇Apache Guacamole作為這類解決方案的代表來研究。前文也提到過,這款產品是市場上最重要的工具之一。許多單位會使用該產品來連接網絡,許多網絡訪問及安全產品也會在自身產品中集成Apache Guacamole,比如Jumpserver Fortress、Quali、Fortigate等。
經過一些背景調查后,我們繪制了典型的網絡架構,如圖1所示:

圖1. 部署Apache Guacamole網關的典型網絡架構
在這種場景中,員工使用瀏覽器連接公司在互聯網上開放的服務器,進行身份認證,然后就可以訪問公司內的主機。員工在使用瀏覽器時,guacamole-server會選擇某種支持的協議(RDP、VNC、SSH等),使用開源客戶端連接企業網內的特定主機。連接成功后,guacamole-server會充當中間人角色,來回傳遞事件,將數據從所選的協議轉換為特定的“Guacamole Protocol”,反之亦然。
了解該架構后,我們可以考慮一些攻擊場景:
1、反向攻擊場景:企業網內部被入侵的一臺主機可以利用入站連接來攻擊網關,希望能控制目標網關。
2、惡意員工場景:惡意員工使用網內主機,利用鏈路兩端節點的控制權來控制目標網關。
0x02 是否需要0-Day
在深入研究代碼之前,我們先簡單了解一下FreeRDP。在之前針對反向RDP攻擊的研究中,我們找到了這款RDP客戶端中的一些漏洞,攻擊者可以通過惡意RDP“服務器”來利用該漏洞。換而言之,企業網內的惡意主機可以控制連接該主機的正常FreeRDP客戶端。我們提供了針對某個漏洞(CVE-2018-8786)的基本PoC,也演示了RCE(遠程代碼執行)場景。
觀察Apache Guacamole的發行版本,可以看到只有2020年1月底發布的1.1.0版中增加了對最新版FreeRDP(2.0.0)的支持。由于FreeRDP只在2.0.0-rc4版中修復了我們發現的漏洞,這意味著2020年1月之前發布的所有版本使用的都是存在漏洞的FreeRDP版本。
因此我們對0-Day漏洞的挖掘暫告一段落,推測大多數企業可能還沒有升級至最新版本,可以使用已知的1-Day漏洞進行攻擊。然而我們還是決定再次挖掘RDP協議中的漏洞,具體研究對象包括:
1、guacamole-server代碼,只關注其中對RDP協議的支持代碼。
2、最新版FreeRDP的代碼:2.0.0-rc4版。
我們設想的利用條件限定于默認安裝的版本,也就是說,只能使用默認情況下處于啟用狀態的功能,并且希望不需要與客戶端進行任何交互。
0x03 尋找新漏洞
由于我們對FreeRDP的代碼非常熟悉,對RDP整體也比較了解,因此很快就找到了一些漏洞。
CPR-ID-2141:信息泄露漏洞
CVE編號: CVE-2020-9497
文件:protocolsrdpchannelsrdpsndrdpsnd-messages.c
函數:guac_rdpsnd_formats_handler()
備注:由于Apache并沒有將我們報告的漏洞(CPR-ID)與他們公布的CVE-ID一一對應,因此我們主要根據他們提供的CPR-ID來對應具體漏洞,這樣更準確一些。
為了在RDP連接以及客戶端之間中繼消息,開發者為默認RDP通道實現了一種擴展。其中有個通道負責獲取服務端的音頻,因此被稱之為rdpsnd(RDP Sound)。
然而在實際場景中,guacamole-server與FreeRDP之間的集成節點很容易出錯。傳入的消息經過FreeRDP的wStream對象封裝,因此相應的數據應該使用該對象的API來解析。然而如圖2所示,開發者忘了設置一個強制條件:傳入的流對象所包含的字節數必須與數據包所聲明的字節數相匹配。

圖2. 缺少輸入過濾導致出現越界讀取
惡意RDP服務器可以發送一個惡意的rdpsnd信道消息,導致客戶端認為數據包中包含大量字節,而這些字節實際上為客戶端內存中的字節。這樣一來,客戶端就會向服務端返回包含這些字節的響應包,導致RDP服務端最終得到了大量數據,實現類似心臟滴血的信息泄露原因。
CPR-ID-2142:信息泄露漏洞
CVE編號: CVE-2020-9497文件: protocolsrdpchannelsrdpsndrdpsnd.c函數: guac_rdpsnd_process_receive()
在這個RDP通道中,有另一個消息還存在類似的漏洞。這次越界(Out-of-Bounds)數據會發送給客戶端,而不是發送回RDP服務端。

圖3. 類似的越界讀取漏洞,向客戶端泄露信息
這個漏洞會將信息泄露給客戶端,我們希望能夠在客戶端不知道網關已被攻擊的情況下,開發出可用的利用代碼。
CPR-ID-2143:信息泄露漏洞
CVE編號: CVE-2020-9497文件: protocolsrdppluginsguacaiguacai-messages.c函數: guac_rdp_ai_read_format()
我們找到了另一個通道:guacai,該通道負責聲音消息(“音頻輸入”),因此名為guacai。盡管該漏洞存在前面類似的漏洞,但默認情況下處于被禁用狀態。

圖4. 與第一個漏洞類似的越界讀取漏洞
此時我們已經找到了3個主要的信息泄露漏洞,應該足以繞過ASLR(地址空間布局隨機化),然而我們仍需要一個內存破壞漏洞來構造完整的利用鏈。由于研究進入瓶頸期,我們開始重新分析FreeRDP,希望能找到之前可能被遺漏的漏洞。
0x04 熟悉的FreeRDP
與上一次相比,RDP客戶端中并沒有太多改動,打上補丁的版本仍然為當時最新的版本。我們先來理解該客戶端涉及到的wStream類型中關鍵要素,該結構的相關字段如圖5所示:

圖5. 用來封裝傳入/傳出數據包的wStream對象
這是一個典型的流封裝結構,包括:
- buffer:指向傳入數據包頭的指針。
- pointer:指向傳入數據包內頭部的指針。
- length:傳入數據包的字節數。
在解析傳入流的指定字段之前,代碼應該先檢查數據流大小是否足以容納相應數據。檢查邏輯如圖6所示:

圖6. 使用Stream_GetRemainingLength()檢查可用的輸入
這個輸入檢查非常重要,每次解析或跳過某個字段時,指針字段都會相應地往前挪動。在執行下一次檢查時對應的代碼邏輯如下:

圖7. 使用當前流的頭部來計算剩余長度
當指針字段越過傳入數據包的末尾時,這個計算邏輯將會向下溢出,從而返回一個巨大的無符號值,代表剩余字節為負數。簡而言之,只要有溢出檢查失效,剩余的檢查過程將毫無意義。這種設計思路導致FreeRDP很容易受到越界讀取漏洞所影響,下文我們將分析這一點。
在提供這類越界讀取漏洞之前,我們先了解一下這些漏洞的重要性。通常情況下,讀取操作只有在以某種方式向攻擊者返回讀取的字節時才有能夠利用。否則讀取操作只能用來訪問內存中未映射的頁面,使程序崩潰。Apache Guacamole攻擊場景比較特殊,這里我們能夠接觸連接的兩端節點。比如,如果內存字節對應的是屏幕的圖形更新數據,那么這些更新數據也會被發送給連接的客戶端。
在這種場景下,每個越界讀取漏洞都有可能變成微小卻有用的信息泄露漏洞。
CPR-ID-2145 & CPR-ID-2146:FreeRDP越界讀取漏洞
由于wStream對象設計中存在缺陷,我們只需要尋找沒有被過濾的讀取操作即可。這個思路很準確,我們找到了兩個漏洞:CPR-ID-2145以及CPR-ID-2146。
然而當我們向廠商報告這兩個漏洞時,我們發現這些漏洞已經由其他研究人員報告過。即使我們只晚了幾個小時,但勝利果實也必須歸他人所有。
因此我們決定讓其他人發表漏洞成果,在本文中不再詳細介紹這些漏洞。
此時我們已經找到了5個漏洞,可以充當攻擊場景中的信息泄露原語。然而我們至少還需要找到一個內存破壞漏洞,而在FreeRDP中尋找這類漏洞會比較麻煩,每次都會被代碼中的檢查邏輯所干擾。很多情況下,這種檢查邏輯針對的都是我們報告過的漏洞,比較無奈。
現在我們的心情與Zensploitation(@zensploitation)的某篇推文非常相似:

圖8. https://twitter.com/zensploitation/status/1244598246879547393
我們畢竟走了不少路,因此絕不輕言放棄。我們決定再次研究一下guacamole-server,這次終于有所收獲。
CPR-ID-2144:內存破壞漏洞
CVE編號: CVE-2020-9498文件: protocolsrdppluginsguac-common-svcguac-common-svc.c函數: guac_rdp_common_svc_handle_open_event()
這個RDP協議會將不同的“設備”當成不同的“通道”,每個設備一個通道。比如聲音對應的是rdpsnd通道,剪貼板對應cliprdr等。作為抽象層,通道消息支持分片,最大支持的消息為4GB。為了正確支持rdpsnd以及rdpdr(設備重定向)通道,guacamole-server的開發者添加了一個附加的抽象層,具體實現位于guac_common_svc.c文件中。該文件中對分片的處理邏輯如圖9所示:

圖9. 處理傳入通道分片的代碼片段
從上圖可知,第一個分片必須包含CHANNEL_FLAG_FIRST片段,當處理該分片時,代碼會根據聲明的消息總長度來分配一個流。
然而如果攻擊者發送的分片不包含該標志時,會出現什么情況呢?代碼似乎會將分片附加到先前的剩余流中,這看上去像是可能存在的一個“懸空指針”漏洞。現在我們只需要檢查開發者在完成前一個分片消息的處理后,是否還記得將該指針置為NULL。

圖10. 在未清除懸空指針的情況下釋放已使用的流
如圖10所示,在完成分片消息重組及解析后,該流會被釋放。也就是說,代碼沒有將懸空指針設置為NULL!
惡意RDP服務器可以發送一個亂序消息片段,使用之前已被釋放的wStream對象,從而變成一個UAF(釋放后重用)漏洞。最重要的是,wStream對象是我們最后可能用來實現該漏洞的對象,因為如果指針字段指向所需的內存地址時,就能用來實現任意寫原語。由于我們在rdpsnd通道中有個信息泄露漏洞,緊跟在惡意wStream對象被使用之后。因此經過努力后,我們可以通過精心構造的wStream對象,將原始漏洞變成更強大的任意讀取利用原語。
RCE(遠程代碼執行)
前面提到過,我們可以利用CVE-2020-9497以及CVE-2020-9498漏洞,最終實現任意讀取及任意寫入利用原文。利用這兩個強大的原意,我們成功實現了RCE(遠程代碼執行)場景,當遠程用戶請求連接網內惡意配合的計算機(也就是我們的RDP“服務器”)時,我們就可以控制guacd進程。

圖11. 漏洞利用結果:使用被控制的guacd進程彈出計算器
但我們的旅途還未結束。guacd進程只處理單個連接,并且以較低權限運行。通常情況下,此時我們需要一個提權(PE)漏洞才能控制整個網關。在與Apache的漏洞協同分析過程中,對方也懷疑這種攻擊場景是否真的可以實現:我們能否通過單個guacd進程來控制網關中所有的連接?
現在我們來尋找這個問題的答案。
“在計算領域,真正了解整個系統的人只有一個:攻擊者”
——轉自Halvar Flake在offensivecon 2020上的演講。
0x05 深入分析Apache Guacamole
如果深入分析前文提到的Guacamole網關,我們可以看到如下結構:

圖12. Apache Guacamole結構中的關鍵節點
在權限提升上,我們重點關注如下兩個組件:
1、guacamole-client:上圖中的Web服務器
2、guacamole-server:上圖中的代理節點
guacamole-client
guacamole-client組件為執行用戶認證的web服務器。這個web服務器保存每個用戶會話所需的配置,存儲的信息包括:
1、所需的協議:通常為RDP。
2、網內員工PC的IP地址。
3、其他信息。
當客戶端成功通過身份認證后,guacamole-client會向guacamole-server發起Guacamole Protocol會話,為客戶端創建匹配的會話。guacamole-client會連接guacamole-server的TCP 4822端口(默認端口),guacd進程在該端口上監聽。
創建會話后,guacamole-client的作用是在guacamole-server以及客戶端瀏覽器之間來回中轉消息。
guacamole-server
根據Apache的官方文檔:“guacd是Guacamole的核心”。在啟動后,guacd會在TCP 4822端口上監聽,等待來自guacamole-client的連接。需要注意的是,該端口上的通信沒有使用身份認證或者加密(支持SSL,但默認情況下并沒有啟用)。因此,我們在圖12中添加了2個防火墻,負責限制對該TCP端口的訪問,只允許來自guacamole-client的連接。
建立連接后,guacd會創建一個新線程,調用負責初始化Guacamole Protocol的函數。此時,用戶有兩個選項可用:
1、創建一個新連接;
2、加入已有的連接。
備注:這里我們使用的是“連接”(connection)而不是“會話”(session),因為這個詞在Guacamole中指的是與指定計算機的連接。每個計算機有一個連接,多個用戶可以共享相同的連接。這里不存在“用戶會話”,因為整個設計理念都基于與特定計算機的連接,用戶只需要加入連接即可。
目前最常用的是第一個選項。此時系統會為新創建的連接生成一個隨機的唯一ID(UUID),然后fork()一個進程來處理。UUID與新進程的對應關系存在內存中名為proc-map的一個字典中,UUID會被發送回guacamole-client。需要注意的是,新生成的進程在啟動與內網主機的連接前會立即釋放權限。
第二個選項比較特別,應該已經實現,這樣多個用戶才能共享一個連接,協同工作。在這種場景中,用戶提供連接的UUID來請求加入已有的連接。為了區分不同用戶,創建該連接的用戶為“所有者”,其他用戶的owner字段被設置為false。這個選項還支持將非“所有者”的用戶連接設置成只讀連接。

圖13. 添加新用戶,在proc-map中存儲進程,允許其他用戶加入
為了支持用戶加入,為特定連接生成的進程會繼承一個socket對,用來與guacd父進程通信。當主線程初始化所需的客戶端時(比如為RDP連接初始化FreeRDP),另一個線程會等待來自父進程的消息,當新用戶要求加入連接時向我們的進程發送信號。
guacd進程充當一個連接管理器角色,為每個連接生成進程,同時也為生成的進程實現了核心邏輯。因此從現在開始,我們將guacd父進程稱之為“主進程”。
0x06 權限提升
步驟0:控制單個guacd進程
前面已介紹過利用方式。
步驟1:偽裝為guacamole-client
雖然我們控制的guacd進程只是運行在網關內的一個低權限進程,但仍具有一些有用的權限。首先,由于該進程運行在網關上,因此我們可以通過TCP 4822端口連接主進程。由于主進程在該端口上沒有身份認證,因此我們可以成功連接,像正常guacamole-client一樣控制該進程。
步驟2:從內存中獲取秘密
這是漏洞利用的關鍵點。由于guacd可執行文件中同時包含主進程以及每個連接進程的邏輯,因此當生成新進程時只用到了fork()。這里我們要強調一點:只使用了fork(),并沒有使用execve()。
這意味著什么呢?fork出來的進程包含父進程的整個內存快照,而當調用execve()時,這個快照會被新的映像所替代。沒有調用該函數時,子進程將繼承父進程的整個內存地址空間。其中包括:
1、完整的內存布局:當我們想攻擊父進程時,這個信息非常有用。
2、完整的內存內容:存放在主進程中的每個秘密信息都會提供給子進程。這意味著我們的進程也擁有proc-map映射,其中包含每個UUID與各自進程的對應關系。我們只需要在自己的內存中找到這個數據結構,就能擁有當前活躍的所有的UUID。
定位proc-map結果只是個技術上的過程。在漏洞利用中,我們從/proc/<pid>/maps中讀取到了我們進程的內存布局文件。該數據結構比較大,因此被mmap()到獨立的內存分配空間,在文件中具有自己的條目信息。
步驟3:加入所有會話
我們已經能夠向主進程發起Guacamole Protocol請求,現在也知道了需要發送哪個請求。接下來我們需要根據連接對應的UUID,來請求加入每個已有的連接。

圖14. 日志顯示我們成功加入了已有的連接
令人驚訝的是,“只讀”會話屬性由guacamole-client來設置。這意味著盡管我們不是連接的所有者,仍然可以關閉“只讀”權限,獲得該連接的完整權限。此外,除了主進程中的日志消息外(圖14),沒有其他信息表明另一個用戶加入了該連接。
步驟4:重復操作
如果大家仔細觀察,可能會注意到我們攻擊計劃中的一個缺點:我們的guacd進程拿到的是過時的proc-map映射信息。如果在我們進程生成后,網關又開啟了其他會話,那么這些會話只會在實際的proc-map中更新,不會反映到我們已過時的內存映像中。
這個缺點解決起來也比較簡單。在指定間隔后(比如每5分鐘),我們可以向主進程發送命令,啟動新的RDP連接,連接網內被我們控制的主機。通過這種方法,我們可以定期生成新的guacd,拿到新版的proc-map。再結合我們原始的漏洞,我們同樣可以攻擊這個進程,“刷新”我們的proc-map。
將這些元素結合在一起后,完成的利用鏈可參考此處視頻,其中我們實現了RCE + PE。
0x07 時間線
- 2020年3月31日:向Apache披露漏洞。
- 2020年3月31日:Apache回應,要求提供更多信息。
- 2020年3月31日:向FreeRDP披露漏洞。
- 2020年3月31日:FreeRDP回應,要求提供更多信息。
- 2020年3月31日:FreeRDP告知我們CPR-ID-2145和CPR-ID-2156都已重復提交,有其他研究者在2020年3月30日分別報告過。
- 2020年5月8日: Apache在某次commit中悄悄修復了漏洞。
- 2020年5月10日:向Apache反饋,表示官方補丁的確修復了我們報告的所有漏洞。
- 2020年5月12日:Apache為我們報告的4個漏洞發布了2個CVE-ID。
- 2020年6月28日:Apache發布了正式補丁版本– 1.2.0。
0x08 總結
這次我們展示了反向RDP攻擊的一個新角度,這是我們在2019年年初提出的一個攻擊場景。用戶通常認為這類攻擊場景影響的是RDP客戶端,而Apache Guacamole給了我們不同感受。在正常情況下,攻擊者可以使用客戶端中的漏洞來控制單個公司網內計算機。然而當在網關中部署時,這類漏洞對企業的影響將更為嚴重。
在疫情期間,遠程辦公是必不可少的一個環節,然而我們并不能忽略這種遠程連接中存在的安全隱患。這里我們以Apache Guacamole為研究目標,成功演示了攻擊者如何借助企業內部被入侵的計算機來控制負責處理所有內網連入會話的網關。成功控制網關后,攻擊者就能竊聽所有的連入會話,記錄所使用的憑據,甚至可以啟動新會話,控制企業內的其他計算機。在目前遠程辦公的大背景下,這個入侵點就有可能讓攻擊者完全控制整個企業網絡。本文翻譯自 research.checkpoint.com, 原文鏈接 。如若轉載請注明出處
我們強烈建議所有用戶使用最新版的服務器,不論大家使用的是什么遠程辦公技術,一定要確保已打全補丁,才能阻止這類攻擊活動。
我是安仔,一名剛入職網絡安全圈的網安萌新,歡迎關注我,跟我一起成長;私信回復【入群】,加入安界網大咖交流群,跟我一起討論網絡安全相關問題~
小白入行網絡安全、混跡安全行業找大咖,以及更多成長干貨資料,歡迎關注#安界網人才培養計劃#、#網絡安全在我身邊#、@安界人才培養計劃。