天天看點

分布式消息系統高危漏洞攻防術

分布式系統大都需要依賴于消息隊列中間件來解決異步處理、應用耦合等問題,消息隊列中間件的選擇又依賴于整體系統的設計和實作,消息的封裝、傳遞、處理貫穿了整個系統,如果在某一個關鍵處理邏輯點上出現了安全問題,那麼整個分布式節點都有可能受到破壞。

流行的開發語言幾乎都存在序列化處理不當導緻的指令執行問題,如 python 裡類的魔術方法 __reduce__()會在 pickle 庫進行反序列化的時候進行調用,php 中類的魔術方法 __wakup() 同樣也會在執行個體進行反序列化的時候調用等等。

從開發者角度看來,開發語言提供的資料序列化方式友善了執行個體對象進行跨應用傳遞,程式a和程式b能夠通過序列化資料傳遞方式來遠端通路執行個體對象或者遠端調用方法(如 java 中的 rmi);而站在安全研究者角度,這種跨應用的資料傳遞或者調用方式可能存在對象資料篡改和方法惡意調用的安全隐患。

在消息隊列的實作中,消息資料的序列化(封裝)方式就成了一顆定時炸彈,不安全的序列化方式可能會導緻消息資料被篡改,進而引發反序列化(資料解析)後的一些列安全問題。

消息隊列中間件的選擇也是一大問題,常見的有 rabbitmq,activemq,kafka,redis 等,而像 redis 這種存在未授權通路問題的元件如果被攻擊者所控制,即可通過元件直接向消息隊列中插入資料,輕則影響整個分布式節點的邏輯處理,重則直接插入惡意資料結合反序列化等問題對節點群進行攻擊。

說了這麼多,總結一下上面提到的幾個安全問題:

各語言中存在的序列化問題可直接導緻遠端指令執行;

消息隊列的實作常常會直接使用語言自身的序列化(或相似的)方式封裝消息;

分布式系統使用的消息隊列中間件種類繁多,其中某些分布式架構使用了像 redis 這種存在着未授權通路問題的元件;

消息隊列中消息的篡改會直接或間接影響到分布式節點。

将這 4 個安全問題或者說是漏洞結合在一起,即可成為一種可直接入侵和破壞分布式節點的攻擊方法。那麼,是否存在真正滿足上述問題的執行個體呢?目前,已經發現了 python 中 celery 分布式架構确實存在這樣的問題,下面我會針對上訴 4 個問題以 celery 分布式架構為例來說明如何攻擊分布式節點打出漏洞組合拳。

一、老生常談python序列化

celery 分布式架構是由 python 語言所編寫的,為了下面更好的說明問題,是以這裡首先簡單回顧一下 python 序列化的安全問題。

1簡單的序列化例子

python 中有這麼一個名為 pickle 的子產品用于執行個體對象的序列化和反序列化,一個簡單的例子:

分布式消息系統高危漏洞攻防術

因 pickle 子產品對執行個體對象進行序列化時産生的是二進制結構資料,傳輸的時候常常會将其進行 base64 編碼,這樣可以有效地防止位元組資料丢失。上面的程式作用是将一個 myobj 類執行個體進行序列化并進行 base64 編碼然後進行解碼反序列化重新得到執行個體的一個過程,運作程式後得到輸出:

分布式消息系統高危漏洞攻防術

通過截圖可以看到序列化前和序列化後的執行個體是不同的(對象位址不同),并且通常反序列化時執行個體化一個類執行個體,目前的運作環境首先必須定義了該類型才能正常序列化,否則可能會遇到找不到正确類型無法進行反序列化的情況,例如将上訴過程分文兩個檔案 serializer.py 和 unserializer.py,前一個檔案用于執行個體化 myobj 類并将其序列化然後經 base64 編碼輸出,而後一個檔案用于接收一串字元串,将其 base64 解碼後進行反序列化:

分布式消息系統高危漏洞攻防術
分布式消息系統高危漏洞攻防術

就上面所說,在反序列化時如果環境中不存在反序列化類型的定義,因為 unserializer.py 中未定義 myobj類,則在對 serializer.py 輸出結果進行反序列化時會失敗報錯,提示找不到 myobj:

分布式消息系統高危漏洞攻防術

2trick 使得反序列化變得危險

看似反序列化并不能執行個體化任意對象(運作環境依賴),但有那麼些 tricks 可以達到進行反序列化即可任意代碼執行的效果。

如果一個類定義了 __reduce__() 函數,那麼在對其執行個體進行反序列化的時候,pickle 會通過 __reduce__()函數去尋找正确的重新類執行個體化過程。

例如這裡我在 myobj 類中定義 __reduce__() 函數:

分布式消息系統高危漏洞攻防術

然後再執行上一節的程式過程,會直接得到輸出:

分布式消息系統高危漏洞攻防術

這裡不再報錯是因為,myobj 在進行序列化的時候,将重新建構類的過程寫進了序列化資料裡,pickle 在進行反序列化的時候會遵循重建過程去執行相應操作,這裡是使用内置的 str 函數去操作參數 'replaced by __reduce__() method' 并傳回,是以成功反序列化并輸出的字元串。

有了 __reduce__() 這個函數,就可以利用該特性在反序列化的時候直接執行任意代碼了,如下示例代碼:

分布式消息系統高危漏洞攻防術

運作得到編碼後的序列化資料:

y3bvc2l4cnn5c3rlbqpwmaoouyd3ag9hbwkncnaxcnrwmgpscdmklg==

這裡需要主要的是 os.system('whoami') 這個過程不是在序列化過程執行的,而是将這個過程以結構化的資料存于了序列化資料中,這裡可以看一下二進制序列化資料:

分布式消息系統高危漏洞攻防術

資料都是以 python pickle 序列化資料結構進行整合的,具體底層協定實作可參考官方文檔。

對上面的序列化資料使用 unserializer.py 進行反序列化操作時,會觸發類重構操作,進而執行 os.system('whoami'):

分布式消息系統高危漏洞攻防術

曆史上架構或者應用由于 python 反序列化問題導緻的任意指令執行并不少見,如 django 低版本使用了 pickle 作為 session 資料預設的序列化方式,在設定了使用 cookie 進行 session 資料存儲的時候,會使得攻擊者直接構造惡意 cookie 值,觸發惡意的反序列化進行任意指令執行;又一些程式可接受一串序列化資料作為輸入,如 sqlmap 之前的 --pickled-options 運作參數就可以傳入由 pickle 子產品序列化後的資料。雖然官方有對 pickle 子產品進行安全聲明,指明了不要反序列化未受信任的資料來源,但是現實應用邏輯繁雜,常會有這樣的資料可控的點出現,也是不太好避免的。

二、分布式架構 celery

回顧完 python 序列化的問題,這時候轉過來看一下 celery 這個分布式架構。

1使用架構進行簡單的任務下發

celery 借助消息隊列中間件進行消息(任務)的傳遞,一個簡單利用 celery 架構進行任務下發并執行的例子:

分布式消息系統高危漏洞攻防術

celery 推薦使用 rabbitmq 作為broker,這裡直接在 192.168.99.100 主機上開啟rabbitmq服務,然後在終端使用celery指令起一個 worker:

celery worker -a celery_simple.app -l debug

然後另起一個 ipython 導入 celery_simple 子產品中的 add() 函數,對其進行調用并擷取結果:

分布式消息系統高危漏洞攻防術
分布式消息系統高危漏洞攻防術

2架構中的消息封裝方式

本文并不關心架構的具體實作和用法,隻關心消息的具體封裝方式。在 celery 架構中有多種可選的消息序列化方式:

pickle

json

msgpack

yaml

可以看到 celery 架構所使用的消息序列化方式中含有 pickle 的序列化方式,上一部分已經說明了 python 中 pickle 序列化方式存在的安全隐患,而 celery 架構卻支援這種方式對消息進行封裝,并且在 4.0.0 版本以前預設使用的也是 pickle 序列化方式。

為了弄明白 celery 的消息格式,這裡将 broker 換成 redis 友善直接檢視資料。

分布式消息系統高危漏洞攻防術

這裡先不起 worker 程序,直接使用 ipython 進行任務下發:

分布式消息系統高危漏洞攻防術

這時候檢視 redis 裡面的資料:

分布式消息系統高危漏洞攻防術

可以看到 redis 裡面存在兩個鍵,celery 和 _kombu.binding.celery,這裡解釋一下具體兩個鍵的具體含義。在 celery 中消息可以根據路由設定分發到不同的任務上,例如這裡 add() 函數由于沒有進行特别的設定,是以其所處的消息隊列為名為 celery 的隊列,exchange 和 routing_key 值都為 celery,所有滿足路由({"queue":"celery","exchange":"celery","routing_key":"celery"})的消息都會發到該 worker 上,然後 worker 根據具體的調用請求去尋找注冊的函數使用參數進行調用。

而剛剛提到的 reids 中的鍵 _kombu.binding.celery 表示存在一個名為 celery 的隊列,其 exchange 和 routing_key 的資訊儲存在該集合裡:

分布式消息系統高危漏洞攻防術

而鍵 celery 裡面存儲的就是每一個 push 到隊列裡面的具體消息資訊:

分布式消息系統高危漏洞攻防術

可以看到是一個 json 格式的資料,為了更友善的進行字段分析,将其提出來格式化顯示為:

分布式消息系統高危漏洞攻防術

在上面的消息資料中,properties 裡包含了消息的路由資訊和辨別性的 uuid 值,而其中properties.body_encoding 的值則表示消息主體 body 的編碼方式,這裡預設為 base64 編碼。在 celery 分布式架構中,worker 端在擷取到消息資料時會根據 properties.body_encoding 的值對消息主體 body 進行解碼,即 base64.b64decode(body),而消息資料中的 content-type 指明了消息主體(具體的任務資料)的序列化方式,由于采用了預設的配置是以這裡使用的是 python 内置序列化子產品 pickle 對任務資料進行的序列化。

将消息主體經 base64 解碼和反序列化(即之前 unserializer.py 檔案功能) 操作以後得到具體的任務資料:

分布式消息系統高危漏洞攻防術

格式化任務資料為:

分布式消息系統高危漏洞攻防術

任務資料标明了哪一個注冊的 task 将會被調用執行,其執行的參數是什麼等等。這裡任務資料已經不在重要,從上面這個分析過程中我們已經得到了這麼一個結論:celery 分布式節點 worker 在擷取到消息資料後,預設配置下會使用 pickle 對消息主體進行反序列化。

3構造惡意消息資料

那麼如果在 broker 中添加一個假任務,其消息主體包含了之前能夠進行指令執行的序列化資料,那麼在 worker 端對其進行反序列化的時候是不是就能夠執行任意代碼了呢?下面就來證明這個假設。

這裡直接對消息主體 body 進行構造,根據第一節回顧的 python 序列化利用方式,構造 payload 使得在反序列化的時候能夠執行指令并将結果進行傳回(友善後面驗證):

分布式消息系統高危漏洞攻防術

運作程式生成具體 payload 資料:

y2nvbw1hbmrzcmdldhn0yxr1c291dhb1dapwmaoouydscyckcdekdhaycljwmwou

使用剛才分析過的消息資料,将消息主體的值替換為上面生成的 payload 得到構造的假消息:

分布式消息系統高危漏洞攻防術

将假消息通過 redis 指令行直接添加到 celery 隊列任務中:

分布式消息系統高危漏洞攻防術

檢視一下 celery 隊列中的消息情況:

分布式消息系統高危漏洞攻防術

然後起一個 celery worker 端加載之前的 celery_simple.py 中的 app,worker 會從隊列中取消息進行處理,當處理到插入的假消息時,會由于無法解析任務資料而報錯:

分布式消息系統高危漏洞攻防術

worker 端經過 pickle.loads(base64.b64decode(body)) 處理對構造的 payload 進行的反序列化,由于 payload 在反序列化時會執行指令并傳回執行結構,是以這裡 worker 端直接将指令執行的結果當作了任務的具體資料,同時也證明了在 celery 分布式架構預設配置下(使用了 pickle 序列化方式),進行惡意消息注入會導緻 worker 端遠端指令執行。

三、利用脆弱的broker代理進行分布式節點攻擊

前面已經證明了 celery 建構的應用中,如果攻擊者能夠控制 broker 往消息隊列中添加任意消息資料,那麼即可構造惡意的消息主體資料,使得 worker 端在對其進行反序列化的時候觸發漏洞導緻任意指令執行。整個流程為:

分布式消息系統高危漏洞攻防術

1檢測celery應用中broker特征

那麼對于這樣一個分布式應用,攻擊者是否能夠輕易的控制 broker 呢?在 celery 支援的消息隊列中間件中含有 reids、mongodb 這種存在未授權通路問題的服務,是以當一個基于 celery 架構實作的分布式應用使用了 redis 或者 mongodb 作為 broker 時,極有可能由于中間件未授權通路的問題而被攻擊者利用,進行惡意的消息注入。

是以,如何去尋找既存在未授權通路問題,同時又作為 celery 分布式應用 broker 的那些脆弱服務呢?根據上一節的分析,已經得知如果 redis 作為 broker 時,其 keys 值會存在固定的特征:

_kombu.binding.*

celery.*

unacked.*

而如果是 mongodb 作為 broker,在其資料庫中會存在這樣的 collections:

messages

messages.broadcast

messages.routing

其中 messages.routing 含有每一個隊列以及消息路由的資訊,messages 則存儲了所有隊列中的消息資料。

分布式消息系統高危漏洞攻防術

那麼就可以根據不同中間件服務的特征去驗證一個 redis 或者 mongodb 是否作為 broker 存在于 celery 分布式應用中。

針對 redis 和 mongodb 可編寫出相應的驗證腳本,其代碼關鍵部分為:

分布式消息系統高危漏洞攻防術

針對未授權的 redis 服務,直接對所有 keys 值進行特征比對,如果遇到其 key 值包含有 ['celery', 'unacked', '_kombu.binding'] 中任意一個字元串即可判斷該服務作為了 celery 應用的消息隊列中間件。

分布式消息系統高危漏洞攻防術

而由于 celery 在使用 mongodb 的時候需要指定資料庫,是以需要對存在未授權通路的 mongodb 中的每一個資料庫都進行檢測,判斷其中的集合名稱是否符合條件,若符合即可判斷其作為了消息隊列中間件。

2使用腳本進行消息注入攻擊分布式節點

使用上面兩個腳本在本地環境進行測試:

分布式消息系統高危漏洞攻防術

這裡要說明的一個問題就是,不是所有使用了 celery 分布式架構的應用都配置了 pickle 的序列化方式,若其隻配置了 json 等其他安全的序列化方式,則就無法利用 python 反序列化進行指令執行了。

一個簡單的針對 redis broker 類型的攻擊腳本:

分布式消息系統高危漏洞攻防術

為了測試攻擊腳本,首先需要在 192.168.99.100 上開啟 redis 服務并配置為外部可連且無需驗證,然後在本地起一個 simplehttpserver 用于接收節點執行指令的請求,最後可直接通過終端配置 broker 為 redis 起一個預設的 worker:

celery worker --broker='redis://:@192.168.99.100/0'

整個過程示範:

分布式消息系統高危漏洞攻防術

可以看到通過往消息隊列中插入惡意消息,被分布式節點 worker 擷取解析後觸發了反序列化漏洞導緻了遠端指令執行。

四、網際網路案例檢測

上一節内容通過實際的代碼和示範過程說明了如何通過特征去驗證消息隊列中間件是否作為了 celery 分布式架構的一部分,那麼網際網路中是否真的存在這樣的執行個體呢。這裡再次理一下針對 celery 分布式節點攻擊的思路:

找尋那有着未授權通路且用于 celery 消息隊列傳遞的中間件服務;

往消息隊列中插入惡意消息資料,因無法确定目标是否允許進行 pickle 方式序列化,是以會進行 payload 盲打;

等待分布式節點取走消息進行解析,觸發反序列化漏洞執行任意代碼;

首先針對第一點,利用腳本去掃描網際網路中存在未授權通路且用于 celery 消息隊列傳遞的 redis 和 mongodb 服務。通過掃描得到未授權通路的 redis 有 14000+ 個,而未授權通路的 mongodb 同樣也有 14000+個。

針對 14000+ 個存在未授權通路的 redis 服務使用上一節的驗證腳本(celery_broker_redis_check.py)進行批量檢測,得到了 86 個目标滿足要求,掃描過程截圖:

分布式消息系統高危漏洞攻防術

同樣的針對 14000+ 個存在未授權通路的 mongodb 服務進行批量檢測,得到了 22 個目标滿足要求,掃描過程截圖:

分布式消息系統高危漏洞攻防術

根據結果來看,雖然最終滿足條件的目标數量并不多,但是這足以說明利用消息注入對分布式節點進行攻擊的思路是可行的,并且在消息隊列中間件後面有多少個 worker 節點并不知道,危害的不僅僅是 broker 而是後面的整個節點。

由于具體的攻擊 payload 使用了盲打,是以不能直接擷取遠端成功執行指令的結果,是以這裡借助外部服務來監聽連接配接請求并進行辨別,若一個分布式節點成功觸發了漏洞,它會去請求外部伺服器。

針對 redis 檢測過程截圖:

分布式消息系統高危漏洞攻防術

其中伺服器上收到了 32 個成功執行指令并回連的請求:

分布式消息系統高危漏洞攻防術

同樣的針對 mongodb 檢測過程截圖:

分布式消息系統高危漏洞攻防術

其中伺服器上成功收到 3 個成功執行指令并回連的請求:

分布式消息系統高危漏洞攻防術

從最後得到的資料來看,成功觸發漏洞導緻遠端指令執行的目标數量并不多,而且整個利用條件也比較苛刻,但就結論而言,已經直接解答了文章一開始所提出的疑問,利用多個漏洞對分布式節點進行攻擊的思路也成功得到了實踐。

寫了那麼多,更多的是把自己平常折騰和研究的一些點給分享出來,理論應用到實戰,沒有案例的漏洞都不能稱之為好漏洞。将一些想法和思路付之于實踐,終究會得到驗證。

相關連結:

celery分布式架構項目 - http://www.celeryproject.org/

python“pickle”子產品文檔 -https://docs.python.org/2/library/pickle.html

django遠端指令執行漏洞詳解 -http://rickgray.me/2015/09/12/django-command-execution-analysis.html

<b></b>

<b>本文來自雲栖社群合作夥伴"dbaplus",原文釋出時間:2016-11-02</b>