淺談分散式滲透框架的架構與設計

本文與大家探討一些關於分散式滲透框架的架構與設計的話題,分享筆者的一些拙見,希望能對大家有所啟發。本文分為三個話題(Topic),建議按順序閱讀,在三個話題中,筆者對兩個痛點(架構設計與通信)做了一些細節描述。

  1. Topic - 滲透測試框架是什麼
  2. Topic - 需求分析與架構設計
  3. Topic - 通信與消息隊列

Topic - 滲透測試框架是什麼

背景

現在很多大小廠家或者很多私人團隊都在做或者已經有成熟的掃描器,開源也好內部使用也好。都是為了解決一些實際的問題:

  • 滲透測試需要
    • 代替重複手動來動
    • 避免遺漏發生
    • 傳承/繼承優秀的測試方法
  • 作為企業的產品之一
    • 面向專業用戶
    • 面向普通用戶
  • 灰色產業(BOTNET)

簡單來說,滲透測試框架能讓你的某些滲透測試流程變的更簡單,輔助業務完成,簡化一些業務邏輯。當然對於框架來說,它的基本功能其實是保證插件的正常運行。當他擁有了很多的插件的時候,價值才會真正體現出來。

本質

滲透測試框架並不是什麼特別高深的東西,相比電商系統中的微服務架構,可能基礎架構並沒有它複雜,甚至相比之下可以說是小巫見大巫了。但是滲透測試框架也並不適合和微服務架構進行對比,因為它更加的靈活,一些高級開發概念(分散式/微服務),對他並不是特別適用。一個滲透測試框架/掃描器,可以非常簡潔明了,甚至可以看成一個靈活的腳本引擎,但是同時又存在著很多的大型的滲透框架/掃描框架(Nessus/OpenVAS),這些框架的複雜程度遠遠高於普通的腳本引擎。說滲透測試框架/掃描器的本質,並不好根據他的特徵說具體它是一個什麼樣的程序:

  • 一個單機運行/分散式的程序框架
  • 高質量的掃描模塊,或者靈活的腳本引擎
  • 穩定的基礎設施,邏輯可編程式控制制

解決的問題

  • 代替某些重複的手工勞動
  • 固定邏輯輸出
  • 穩定的輸出

不能解決的問題

  • 帶有複雜邏輯的漏洞無法檢測
  • 分散式滲透框架設計與實現難度高

普適性的規則

對於一個這種框架來說,再簡化簡化簡化模型,它最終會變成一個 RPC(Remote Procedure Call)調用多種程序(功能單元)的框架,當然這裡不是說具體的 RPC 協議,而是一個泛化的 RPC 的概念;或者變成一個直接調用多種程序(功能單元)的框架。

與普通 RPC 最大的差別就是:任務種類多樣,任務可能存在更新的情況,執行任務環境非常複雜。而且框架本身需要支持高度靈活的功能單元(插件)。或者用另一句話來說,被調用的程序(功能模塊)對於框架來說,是非常非常鬆散的,甚至失效/過時都不會影響任何框架的運行。

這樣來說,它又不能簡單的說是一個 RPC,基於上述的情形,他在設計的時候,排除後期模塊註冊等機制,框架本身是不能主動知道他會有多少種類,多少數量的程序可以讓他在遠程調用,同時也不知道這些程序的基礎介面到底是什麼。因此用 RPC 來描述,又不是特別恰當。

那麼,在對於框架來說,我們最好採用哪種形式來描述?當然,每個人有每個人的看法,我更偏向於使用 鬆散的主從架構(Loose Master-Slave) 這樣的描述來作為這類框架的架構模型:自然 Slave 表示功能單元或者被調用的程序,Master 則代表控制器/調用者或者整體事務邏輯處理的節點,Loose 則代表了 Slave 和 Master 之間的關係,相互的依賴性不是特別的強,可以採用其他的機制來維護他們之間的聯繫,比如註冊或訂閱機制等。

補充說明

當然框架和滲透測試工具是不一樣的,工具大可直接使用分層架構,甚至可以不用太注意項目模型和結構相關的部分,直接面向過程實現某一些特定功能,或者部分或者整體使用微內核架構提供一定的靈活性。

現主流掃描器/框架的模型分類

  • 單機與分散式:本文中單機與分散式的最大的區別不是是否聯網,而是具體的執行任務的工作節點是否在一台機器上。
  • 微內核架構:這種架構非常靈活,既可以作為部分也可以作為整體框架使用。比如常見的腳本引擎:Nmap 的 NSE 系統,SQLMAP 的 Tamper 機制等等,其實可以說用到了微內核模型的一些思想。
  • 微內核與主從:微內核採用主從模型,在一定程度上解耦功能模塊。

在下一個話題中,我們討論分散式滲透框架/掃描器的架構設計相關的話題。

Topic - 需求分析與架構設計

背景

簡單來說這個框架的任務就是,接收任務,分發任務,執行任務,處理結果,因此分散式滲透測試框架相對於電商平台的微服務架構要簡單很多,因為滲透測試框架的事務相對更佳簡單一些,甚至可以說淡化事務這個概念。但是又屬於任務密集型的應用,一定程度上高並發和高可用性又存在一定的需求。

我們可以從頭梳理一下整個過程,Master 得到一個任務,這個任務被分為幾個原子任務,按照種類被送給了不同的 Worker,然後任務執行之後,結果被發送會 Master,進行事務匯總。

整個過程雖然並不是非常的複雜,但是我們還是需要分析一下我們到底需要什麼才能針對需求作出合理的架構設計

需求

思考了很多關於需求描述的方法,我們沒有辦法只從一個角度很好的描述我們的系統到底需要什麼樣的功能,這就好比我們有了 OOP,但是 AOP 的出現進行了對 OOP 非常好的補充,當然還有 SOP(面向狀態編程)也對 OOP 的起到了很好的補充。啊,話題扯遠了,經過一些思考,分別從空間和時間(過程/邏輯)兩個角度來討論我們的需求。

  • 空間角度是指對象,行為,數據,實體角度與傳統的 OOP 的思考角度類似,就不做過多的贅述了。
  • 時間/過程/邏輯角度指的是我們從個行為從開始到結束整個過程來思考問題,就比如,從 Master 到 Slave 通信的整個過程,一個事務的生存周期(任務從產生到結束或者被丟棄),一個模塊的生存周期,一個存在主邏輯的調度過程等。

空間需求

從功能實體的角度來說,我們大概需要這樣的東西:

Master

  • 事務管理
    • 任務接收
    • 任務分發
    • 結果收集
  • 節點管理
    • 節點審計(Inspect)
    • 節點啟停(Start / Stop)
  • 用戶管理

Slave

  • 狀態無關
  • 事務處理
    • 任務執行
    • 結果返回
  • 節點管理
    • 節點狀態彙報

時間/過程/邏輯需求

當然這裡指的是本框架中為了解決問題從而需要的一系列的機制或者可能出現的過程或者邏輯

  • 通信系統(稍後會詳細討論)
  • 事務系統
    • 任務管理系統
    • 結果管理系統
  • 功能模塊引擎
  • 高度模塊化:指的是模塊之間高度獨立,極低耦合甚至沒有耦合
  • 使用腳本引擎提升靈活性:針對不必要新建模塊的小型任務,僅使用一個腳本引擎來敏捷啟動和執行任務
  • 對框架的低依賴:模塊按照一定規範編寫而成,不需要向框架提供任何介面
  • 介面統一,但是模塊本身環境的多樣性:模塊可以是任意的語言,任意的環境,任意容器,但是模塊的介面必須是框架可以接受的
  • 調度系統

存在一個可以讓模塊協同工作的調度系統。

  1. 用戶介面(略)
  2. 框架擴展系統
    1. 任務腳本 SDK
    2. 關鍵點/關鍵消息隊列拓展

設計

根據我們需求,最簡單的,我們整體的架構應該是一個鬆散的 Master-Slave 架構,Master 並不依賴任何 Slave,但是 Slave 必須依附於 Master 才可以工作。

在進行下面的敘述過程中,我們首先約定一下在我們的框架中要出現的幾個概念:

  • Master - 主控實體
  • MQ 連接 Master 與 Slave 所有服務通信的中間件
  • Slave - 具體執行業務的實體
    • 功能單元:具備一種執行任務的能力,但是只能執行一種,可以直接向 Master 彙報生存狀況和結果,脫離節點也可以存在
    • 節點:可以管理多個功能單元,但是不實現執行任務的介面,也並沒有執行任務的能力,可以直接向 Master 彙報生存狀況和結果(結果來源於功能單元)

好的,我們用下面這個圖來簡單說明一下結構

同時,這個整體的架構並不夠我們描述整個框架,也顯得非常敷衍,所以我們很有必要做詳細的闡述:

Master 拆分服務與子功能

Master 是一個巨大的功能集合,但是並不是一個「牛類」這樣的東西,Master 是由很多個服務構成的,因此我們非常有必要把 Master 拆分成具體的服務來分別闡述其用途。

  • 通信服務
  • 節點和模塊管理服務:提供對 Slave 的管理功能
    • 節點管理子服務
    • 模塊(功能單元)管理子服務
  • 模塊調度服務:調度模塊/功能單元之間的協作邏輯
  • 事務處理服務
    • 任務處理子服務
    • 結果處理子服務
  • 持久化服務:存儲任務和結果
  • 用戶服務
    • 用戶鑒權與管理(後期)
    • 用戶介面

Slave 拆分服務與子功能

  • 節點服務
    • Master 通信服務
      • 控制信道:傳遞主控節點的控制信息
      • 任務信道:任務接收信道
      • 結果信道:結果彙報
      • 彙報信道:額外信息彙報
      • …...
    • 功能單元管理子服務
    • 功能單元使用子服務
  • 功能單元服務
    • 接收任務
    • 檢查參數與合理性
    • 執行任務
    • 返回最終結果或者階段性結果

其他設備

  • 消息隊列(集群):在下一個 Topic 會著重探討。

過程設計

設計對應需求,我們仍然需要對重要的過程進行設計,和上面服務/功能拆分是不同的角度,但是也很好的可以描述出框架工作的過程。所以我個人覺的這個角度來做一些說明是非常有必要的。

事務處理流程

事務處理不論是在各種門戶的微服務架構中還是在電商微服務架構都起著非常重要的作用:

事務的存在是為了保障任務執行的完整性和精確性,舉個例子說明事務的存在的必要:當你的事情開始執行了,但是由於節點崩潰或者網路原因,沒有辦法成功執行這個操作,在收到執行失敗的信號之後,事務會回滾到上一個安全的狀態,這樣就避免了 Pending 這種薛定諤狀態,也是最終一致性的一種體現或者實現方法吧(當然事務控制中心我們這裡只設置一個,就不存在多個事物控制中心數據的需要強同步的問題了)

我們在設計這個框架的時候有意將事務這個概念引入我們的框架,運用最終一致性處理事務處理

功能單元生存周期

當然理應所有的功能實體都應該有一個狀態機,但是由於篇幅所限,我們就舉個簡單的例子,功能單元的生存周期狀態圖如下

在功能單元啟動之後,首先進入初始狀態,進行初始化,初始化成功則進入 Prepared 狀態,如果初始化失敗,造成功能單元崩潰,則進入崩潰處理流程。

在 Prepared 狀態下,功能單元自動發起註冊到 Master,如果註冊成功,改變狀態為 Registered,如果失敗,進入 Unregistered 狀態。

Registered 狀態直接進入 Working 狀態,Working 狀態下,會定時發送心跳(或者其他機制來保證與 Master)連接,如果發生多次連接斷開,則進入 Unregistered 狀態。同時在 Working 狀態下進行事務處理(略)。如果事務處理過程中程序遭遇到不可解決的崩潰,則進入 Crashed 狀態。

Crashed 狀態下,我們需要重置功能單元決定是停止功能單元,還是重啟。

Unregistered 狀態會自動關閉功能單元,因為 Unregistered 是一個標誌著功能單元正常結束的單元。

在上一個話題中,我們關於消息隊列(通信)的部分並沒有做太多的描述,接下來我們就在下一個話題中具體闡述一下關於節點通信的問題。

Topic - 通信與消息隊列

本部分與大家簡單探討本框架的架構與消息隊列的關係和對消息隊列的設計。

消息隊列基礎

參考資料:tech.meituan.com/mq-des

RabbitMQ: rabbitmq.com/reliabilit

必要性

對於我們的滲透測試系統,消息隊列真的是必須的么?我個人的回答是必須的。

主要特性:解決服務(模塊)通信問題

通信不僅僅是 「我發送,你收到」 這麼簡單的事情。從本文框架的架構整體上來說,是一個 Master / Slaves 的架構模式,也就是說一個 Master 和多個 Slaves 同時進行通信,當然通信的種類也多種多樣:

  • 任務分發通信(多對多):Master 向 Slaves 分發任務,這個通信模型更像是一個 Producer / Consumer 的模式,這很好理解,Master 發送任務,Slaves 執行,我們自然保證並不想多個 Slave 同時來做一個任務,這樣就白白浪費了資源。
  • 通知與訂閱消息通信(一對多):可以簡單想像一下廣播與組播的需求,這種通信模型我們可以暫且稱之為 FanOut 吧。其實這也非常好理解,當你的 Master 想要發送一個通知消息,這個消息可能是針對全體的 Slaves 的,這樣最合適的辦法就是使用廣播通信模式;同樣的,當你的 Master 想要針對某一個組發送通知(例如關閉所有的爬蟲模塊組,升級某一個組的資料庫,或者更新代碼,部署新的功能),這類通知你是不希望被其他無關組或者無關節點收到的。針對這些情況,FanOut 可以很好解決。
  • 點對點(一對一):這裡主要不涉及 Slave 與 Slave 的通信,我們的架構似乎並不喜歡 Slave 與 Slave 之間有聯繫,這樣會極大增加耦合度和複雜度;但是 Master 到 Slave 的單點通信時必須要有的,因為我們經常會需要單獨告訴一個 Slave 應該幹啥(關閉 Slave,重啟 Slave 甚至 升級 Slave)
  • 結果彙報與生存狀況彙報(多對多):作為無狀態的 Slave,完成一個任務的第一件事,應該就是把任務傳回 Master;當然,作為 Master,是有必要知道 Slave 的一些生存狀況的,除了主動問詢的方法之外,Slave 還應該主動向 Master 進行彙報;同時,Slave 的關鍵部分掛掉了,錯誤信息/日誌,也應該傳回 Master…… 這樣的需求其實一點都不過分,我們需要一個 FanIn 的模型去解決這種問題。

次要特性:可靠性/安全性

  • 可靠性:在正常工作的條件下,你的通信兩端拿到的數據是無差別的不會出現數據的差別,並且不會無故丟失數據,出現不期望的數據。你可以信賴你的數據來源。
  • 安全性:不希望數據被別人截取造成信息泄漏,或者因為反序列化漏洞造成 RCE,或者命令注入,或者未知的風險,通信需要支持 SSL

而一個可靠的消息隊列,它本身會有一整套的機制保證消息從一端到另外一端是可靠的,你可以不必擔心你的消息在通信的過程中丟失/在消息隊列中丟失(因為機器重啟或其他不可預料的因素)。我們以 RabbitMQ 做例子,舉例一下 RabbitMQ 在數據可靠性和一致性上做的一些工作:

Ensuring Messages are Routed

In some circumstances it can be important for producers to ensure that their messages are being routed to queues (although not always - in the case of a pub-sub system producers will just publish and if no consumers are interested it is correct for messages to be dropped).

確保消息一定是被路由處理的:在絕大多數情況下,RabbitMQ 都可以讓生產者確認消息通過路由已經被傳遞進了消息隊列中(除了沒有訂閱者的 發布-訂閱 系統)

At the Consumer

In the event of network failure (or a node crashing), messages can be duplicated, and consumers must be prepared to handle them. If possible, the simplest way to handle this is to ensure that your consumers handle messages in an idempotent way rather than explicitly deal with deduplication.

在節點崩潰或者網路錯誤的時候,消息可能會出現重複,與此同時消費者必須對重複有解決辦法。如果想解決這個問題,最簡單的方法就是使用冪等這種方法(而不是直接處理)。

If a message is delivered to a consumer and then requeued (because it was not acknowledged before the consumer connection dropped, for example) then RabbitMQ will set the redeliveredflag on it when it is delivered again (whether to the same consumer or a different one).

...

Conversely if the redelivered flag is not set then it is guaranteed that the message has not been seen before. Therefore if a consumer finds it more expensive to deduplicate messages or process them in an idempotent manner, it can do this only for messages with the redeliveredflag set.

如果一個已經被送到了接收方,但是因為沒有 ACK,消息會被重新進入消息隊列;但是如果你想讓消息再回到這個沒有 ACK 的接受方,你需要向讓你的消息設置一個 Redeliveredflag,如果這樣的話,你的接收方會重新接受到那個沒有 ACK 的信息。

反過來說如果 redelivered flag 沒有被設置的話,就可以確保你接收方都不會受到重複的消息了。因此吧,我們其實並不是必須在應用業務層進行冪等方法,可以簡單的使用消息隊列的這個特性。

選型(可編程的協議 - RabbitMQ)

我們在這裡選擇 RabbitMQ 作為我們消息隊列支持,接下來結合 RabbitMQ 的特徵和我們的框架特性,我們可以嘗試簡單設計一下關鍵的消息隊列結構。其實對於我們的 RabbitMQ 來說,消息隊列的設計也會相當愉快,我們的發送方其實是不知道接收方具體的消息隊列的,消息隊列其實只是一個存在於接收方的概念。在發送方,只有交換機和路由的概念。所以我們可以使用不同交換機的種類配合路由來實現。

交換機

在 RabbitMQ 中,交換機有四種類型:

  • Direct Exchange(直連交換機):由 routing_key 提供一對一的直連服務,這樣就可以解決 Master 到單個節點或者功能單元的問題。
  • Fanout Exchange(扇出交換機) :提供一(一個交換機)對多的消息交換服務,可以解決 Master 到所有節點的通知問題,但是這個需求實際上並不是特別的必要(除了整個系統在進行大的升級/關閉的時候),並不是非常常用。
  • Topic Exchange(主題交換機) :這個其實也可以說是和 Fanout Exchange 有一點類似,只要訂閱了一個主題就可以收到這個主題的相關信息了,當然訂閱主題的方法也十分靈活。我們可以通過這個交換機,向各個組/ 類型/具有某個預設特徵的功能單元或者節點發送消息。
  • Header Exchange(首部交換機) : Direct Exchange 的另外一種表現形式,只是不是使用 routing_key 進行路由的,是由本身的一個 headers 來控制的。

根據這四種類型的交換機,我們很容易設計出我們的通信系統。(當實際的使用,我們並不會全部選擇)

下面依次對幾個交換機做簡要說明:

  1. 任務分發交換機( Direct Exchange ),每一種類型的任務是一個路由鍵,節點需設置這個路由鍵才能收到任務。在客戶端,每一類型的功能單元要設置相同的路由鍵(類型名),才可以接收到任務
  2. 結果收集交換機( Topic Exchange ),日誌記錄系統和審計系統或者其他系統都可以通過這個結果收集交換機訂閱到自己想要查看的結果。同樣,如果開發新的服務或者模組也可以通過訂閱結果交換機去獲取任務執行的結果。
  3. 控制交換機( Direct Exchange ),直接管理節點的行為(啟動/停止/更新),當然這個控制交換機的路由鍵為這個節點的 GUID 或 UUID,這樣可以實現 Master 到 Slave 的單點鏈接。
  4. 通知交換機( Topic Exchange ),按功能單元的類型分組,控制交換機的批量操作(對某一種類型的功能單元進行更新資料庫,熱補丁/批量關閉)。
  5. 反饋交換機( Topic Exchange ),同結果交換機。

其中,任務分發和結果收集是屬於事務(任務)管理服務的,控制/通知/反饋交換機是屬於節點(功能單元)管理服務的。這樣我們可以把管理與業務,通過消息隊列進行完美分離。

上文描述了一個分散式滲透框架該有的部分,但是限於篇幅,我們沒有辦法把每一個部分都的設計思路都描述清楚。筆者能力有限,文中如有紕漏,希望讀者不吝賜教。

PS: 知乎的編輯器莫名其妙粒子態吃掉列表的 Tab 導致列表出現格式 Bug,筆者經過嘗試無法修好 ??

推薦閱讀:

常見惡意軟體工具分析(一)

TAG:网络安全 | 黑客工具 | 黑客Hacker |