灰盒自動化漏洞挖掘實踐
轉載請註明出處並附上源鏈接
版權所有,侵權必究
我們專註漏洞檢測方向:danenmao、arnoxia、皇天霸、lSHANG、KeyKernel、BugQueen、zyl、隱形人真忙、oxen(不分先後)
摘要:本文主要介紹通過Hook技術和漏洞Fuzz的方式來構建灰盒自動化漏洞挖掘系統的技術原理與細節,並給出企業級部署實踐與檢出效果。
作者:隱形人真忙& KeyKernel
一、什麼是灰盒掃描
傳統掃描器的原理實際上是構造一些探測串,一般稱之為載荷(payload),用這些構造出來的數據進行組包並發送給伺服器,當伺服器返回數據或者做出的反應符合某種判定規則時,就認為有漏洞。這個過程是一個黑盒探測的過程,並不涉及到服務的具體邏輯,而且受網路通信的制約較大——如進行延時SQL注入掃描時,網路延遲的大小直接影響掃描的準確性。此外,基於黑盒的傳統掃描往往需要發送大量的數據包,如布爾型SQL注入漏洞掃描中,需要構造大量的閉合語法掃描payload,可能僅僅檢測一條URL就要發送1000個以上的請求。
灰盒漏洞掃描正是為了彌補傳統黑盒掃描的不足,通過HOOK需要檢測的敏感函數,可以在web應用運行時獲取到對應的危險參數,配合Fuzz技術,可以更加高效、準確地發現漏洞。
二、灰盒掃描架構設計
本節以PHP語言為例,來講解如何構建一個適用於PHP的動態灰盒掃描系統。灰盒掃描系統一般分為三部分:
(1)HOOK部分
主要實現對一些危險函數進行HOOK,確保在運行時環境中能夠獲取到傳入函數的任意參數。
(2)Fuzz部分
主要生產一些精心構造的污染數據,比如包含一些特殊符號的字元串等。通過掃描發包的方式將臟數據插入到各個待檢測的地方。
(3)規則部分
主要負責對漏洞的識別。通過和Fuzz系統進行配合,檢查HOOK到的參數是否符合判別規則,從而判斷是否存在漏洞,如果存在則發起上報流程。
這三個模塊如下圖所示進行整合:
當然,基於fuzz的思路也有缺點,那就是非常依賴於待測web應用的URL輸入源的質量。如果URL不夠完整,很可能會漏掉某些代碼片段,無法做到像靜態代碼掃描一樣的全面檢查。事實上,這也是靜態分析技術和動態分析技術的區別,即動態分析技術的代碼覆蓋很難做到非常全面。
三、構建灰盒掃描系統
3.1 構建HOOK層
3.1.1 HOOK原理
3.1.1.1 Zend和PHP
PHP是一個解釋型語言,我們編寫的PHP腳本文件需要解釋器進行解析,生成中間代碼然後執行。實現一個解釋語言一般需要三個部分:
(1)解釋器部分:分析輸入的代碼,翻譯該代碼,然後執行代碼。
(2)功能部分:完成語言的一些特性和功能(如函數、類等等)
(3)介面部分:提供外部統一交互介面(如與Web通信等)
Zend引擎是PHP實現的核心,它完成了解釋器部分的實現,並且部分參與PHP的功能部分實現,如一些PHP語言的實現、擴展機制、內存管理機制等。Zend引擎與PHP的關係圖如下:
通常我們都選擇使用實現一個擴展的形式來擴展PHP,在PHP擴展中,我們可以調用Zend API做很多定製化的功能,包括實現對PHP內置函數調用的HOOK操作。
3.1.1.2 PHP Opcode
PHP是一種解釋型語言,運行一段PHP代碼通常包含兩個階段:編譯和執行,其中編譯部分包含語義分析、語法分析等階段,這些功能由Zend引擎負責提供。一段PHP腳本文件經過Zend內核的處理,會變為一系列的操作碼,稱為opcode。這是一種Zend內核可以理解的中間語言,在執行階段就是去執行這些opcode完成操作。
我們可以使用vld擴展(http://pecl.php.net/package/vld)來查看某段PHP腳本代碼的opcode,安裝方法如下:
安裝完成後,編輯一段PHP代碼如下:
執行下面的命令輸出opcode:
可以清晰地看到在Zend內核中,針對echo是有一個名為「ZEND_ECHO」的opcode對應的。通過vld工具,我們可以看到函數調用過程中,Zend是分配了一個名為」ZEND_DO_FACALL」的opcode進行處理。
Opcode對應的結構體定義在{$PHP_HOME}/Zend/zend_compile.h,{$PHP_HOME}代指本地的PHP源碼文件所在路徑,下文的描述中統一使用這個替代符。這裡以PHP 5.4為例,低版本的PHP如5.2中opcode的定義截然不同
每個opcode對應一個opcode_handler_t類型的函數,該函數用於實現opcode對應的操作。Handler函數定義可以在{$PHP_HOME}/Zend/zend_vm_execute.h中找到,比如ZEND_ECHO指令的一個handler函數如下:
PHP內核的知識非常之豐富,但畢竟不是本文的重點,編寫健壯、可持續運行的HOOK機制還需要對Zend內核源碼更加細緻的研究才能實現,這裡由於篇幅限制不進行贅述。
3.1.2 Hook實現
對PHP內置函數進行HOOK以獲取運行時的函數參數,一般來說有下面三種方式:
(1)對特定的Opcode進行HOOK
(2)對ZEND_DO_FCALL進行HOOK
(3)對PHP_FUNCTION實現函數進行HOOK
下面分別介紹這三種方式如何實現函數的HOOK
3.1.2.1 Hook FCALL
在PHP內核中,每一個OP操作都是由一個固定的op Handler函數去負責的,即_zend_op結構體的handler屬性,表示該OP對應的op handler函數。
我們可以通過調用zend_set_user_opcode_handler,將對應的Zend Opcode的handler函數替換成自己定義的函數實現HOOK機制:
當PHP執行ZEND_ECHO(即調用echo輸出字元串)的時候就會調用我們的handler函數,這樣就實現了HOOK,完成我們自定義的操作後,將opcode的處理轉給默認的Opcode handler函數即可。
很多PHP內置函數的調用底層都是ZEND_FCALL進行實現的。
因此,我們需要HOOK兩個opcode,ZEND_DO_FCALL和ZEND_DO_FCALL_BY_NAME,後者是通過動態參數調用的opcode。
而在我們自己的handler中,可以通過獲取這兩個opcode函數名稱來對不同的函數進行hook操作。在PHP 5.4版本中,HOOK思路如下:
(1)從EG(current_execute_data)->function_state.function中獲取原有的函數。
(2)然後對函數調用和類方法調用分別來執行HOOK操作
(3)HOOK完成後,恢復原有函數,將function_state.function重新賦值
注意,對於不同的PHP版本,所使用的結構體是不一樣的。
在執行函數調用時,函數參數是保存在一個Zend虛擬機的棧結構中,因此我們需要移動指針來獲取函數中的所有參數。
在FCALL的自定義handler中,函數參數分布圖如下:
參數放入了EG(argument_stack)結構,因此首先獲取一個棧頂指針:
獲取函數參數個數方法如下:
拿到參數個數之後,我們就可以操作棧頂指針獲取函數的每個參數了:
獲取到所有的參數之後,我們就可以結合Fuzz規則進行漏洞識別了。
3.1.2.2 Hook Opcode
PHP中的一些「函數」的實現比較特殊,比如echo、include、eval等,這些函數在PHP內核中是直接調用特定的Opcode來執行的,如果只對FCALL指令進行HOOK,顯然是不完整的。因此我們需要額外處理下列的Opcode:
具體實現形式和HOOK指令ZEND_DO_FCALL一致,唯一的區別在於獲取參數的方式有所不同,在上一節中,我們通過一個Zend里的棧結構去獲取到函數參數,而這次我們需要從Opcode的操作數中獲取參數。
獲取操作數的大致思路是,首先從zend_op結構中獲取op1或者op2,然後根據op1_type或者op2_type分情況抽取參數值:
(1)IS_TMP_VAR
如果op的類型為臨時變數,則調用get_zval_ptr_tmp獲取參數值。
(2)IS_VAR
如果是變數類型,則直接從opline->var.ptr里獲取
(3)IS_CV
如果是編譯變數參考ZEND_ECHO_SPEC_CV_HANDLER中的處理方式,是直接從EG(active_symbol_table)中尋找。
(4)IS_CONST
如果op類型是常量,則直接獲取opline->op1.zv即可
上述方法都是從PHP源碼中選取的,比如一個ZEND_ECHO指令的Handler會有多個,分別處理不同類型的op,這裡有:
比如我們可以通過閱讀ZEND_ECHO_SPEC_TMP_HANDLER這個handler函數的源代碼找到獲取TMP類型操作數的方法:
通過調用_get_zval_ptr_tmp這個API即可,類似的,其他類型的操作數獲取也可以按照這個思路來實現。
3.1.2.3 Hook PHP_FUNCTION
PHP中大多數內置函數是通過實現內部擴展的形式完成的,比如內置的
mysql_get_client_info函數就是通過擴展的形式實現的,其內部源碼如下:
針對PHP_FUNCTION的HOOK流程大致如下,以HOOK system函數為例:
(1)將system函數的別名改為我們自己的PHP擴展函數hook_system;
(2)從當前function_table結構中找到system函數指針;
(3)將system重命名為hook_system,用這個名稱保存步驟1獲取到的函數指針,保存在function_table結構中;
(4)將system函數從function_table結構中刪除;
(5)當外界調用system函數時,會調用hook_system函數,在該函數中完成參數提取和漏洞判定操作;
(6)恢復之前的函數指針,保證system函數正常執行
通過這種方式進行HOOK,可以很方便地使用zend_parse_parameters獲取到
函數的參數,缺點是每個需要HOOK的函數都需要編寫一個額外替換的擴展函數,維護起來比較繁瑣。
3.2 構建Fuzz層
Fuzz系統主要負責構造一些特殊字元串,利用黑盒掃描的形式將其發送至安裝了HOOK層的Web伺服器,目的是與HOOK層的漏洞判斷邏輯進行配合,比如傳入一些特殊字元,如果這些特殊字元串進入危險函數的參數中沒有發生有效的變形,則認為漏洞產生。
由於每次調用敏感函數,都會執行一次漏洞判定,因此為了性能考慮,Fuzz規則盡量做成較為簡單且有效的形式。主要有三種設計思路:
基於特殊字元
很多Web漏洞,如SQL注入漏洞,在攻擊時都需要引入特殊字元(如單引號、雙引號、括弧、分號等)進行語法閉合,因此很多過濾程序會對這些特殊字元進行凈化或者轉義處理。
因此我們可以通過掃描的形式向web應用傳入一些特殊字元,比如針對SQL注入,我們發送字元串;).,「(」(;『,如果後端調用mysql_query,HOOK層獲取到其參數中原樣存在這個字元串,就可以初步判斷為漏洞。
基於特徵字元串
除了特殊字元,還可以提前預定Fuzz的命中模式。比如針對SSRF漏洞的檢測,我們可以傳入一個內網的URL,當HOOK層獲取到某些敏感函數的參數時,當參數中原封不動的存在該URL,就可以認為程序內部實際上沒有有效的校驗,否則也不會執行到調用敏感函數發起網路請求的代碼。因此,這種情況也可以認為是漏洞命中。
基於複雜邏輯判斷
某些更加複雜的規則是無法簡單地用特徵串的形式,比如檢測文件上傳漏洞webshell等情況,需要獲取一些額外的程序執行信息才能執行判斷,這就需要做成類似於「掃描插件PoC」的形式。
四、灰盒掃描形態與部署
灰盒動態掃描本質上是為了彌補傳統漏洞掃描的不足,從動態運行時的維度切入,來增強漏洞發現能力。同時,灰盒掃描不需要很複雜的判斷邏輯就能獲得較高的準確率和較低的誤報率。
灰盒掃描實現中,可以將HOOK層和漏洞判斷功能進行分離,這是因為HOOK層的變動往往需要重啟fastcgi或者伺服器,如果漏洞判別邏輯經常變動,會造成頻繁重啟。因此需要單獨的agent進行實現,或者直接傳遞至雲端進行分析處理。所以灰盒掃描系統的大致形態如下:
在甲方安全實踐上,一般可以將其部署到線下測試環境,最理想的形態是和上線前階段的漏洞掃描進行深度結合,與傳統漏洞掃描進行相互補充,從而將大多數常規漏洞消滅在測試階段。
對於線上環境,灰盒掃描往往會消耗伺服器大量的資源,尤其是每次執行危險函數都會執行一次HOOK和漏洞判別操作,對性能的損耗很大。如果其損耗在業務方可接受的範圍內,將灰盒動態掃描和公司內部的日常漏洞掃描進行結合也是一個不錯的部署方案
4.1.1 灰盒掃描HOOK選擇
在灰盒掃描中,覆蓋更多的函數意味著擁有更強的檢測能力,但是同時也更多的影響了服務的運行。
通常,在測試環境中,我們使用HOOK opcode的方式,提升檢測能力,並且同時也可以在測試環境中驗證灰盒的檢測規則以及能力。
HOOK opcode執行時對性能損耗比較大,這時一般我們對我們不感興趣的流量進行放行,對不感興趣的函數進行放行。可以將性能損耗降低在10%以下。但是這種損耗,對於線上業務是不可接受的。
對於線上業務,採用定製HOOK特定函數的方式來實現,實測使用wordpress每次頁面請求產生20次函數攔截的推送情況下只有會對php端產生3%以內的耗時影響,無推送時只有1%內的耗時影響。對整體業務的性能影響較低。
4.1.2 信息收集難點與解決方式
獲取了惡意流量,得到了觸發漏洞的場景,我們需要對該場景進行收集和上報,供安全工程師已經業務線查看。對於上報和收集在PHP場景下,比較困難。原因有:
PHP頁面開始結束的時間固定,頁面結束後,無法做到優雅的延遲上報。
通過收集的場景信息,計算這個信息大小,如果每個頁面超過幾千kb,信息接收方也很難處理,同時對機器的CPU和內存都很有挑戰。
可能需要開啟其他的惡意信息上報,這個上報的信息同樣很大。
受於上述原因,我們使用了Agent + so的搭配,讓Agent常駐系統中,開闢IPC通訊給so使用。so將收集到的數據傳遞到Agent。IPC通訊時,不建議使用有鎖通訊。針對性能這塊,只能慢慢嘗試調優,將性能調整到預期的水平即可。
五、漏洞檢出效果
事實上,對於一些注入類型的漏洞,比如SQL注入、命令注入、代碼注入等,傳統的掃描都需要觸發內部的執行條件,比如最起碼要成功閉合語法結構等。但是由於後端代碼一般場景複雜,所以傳統掃描會有很多由於場景缺失而造成的漏報和誤報。
但是使用灰盒掃描就可以顯著增加這些複雜場景的漏洞檢出率,比如遇到insert、update、delete注入以及一些字元串變形的場景,也可以很準確地定位到漏洞:
目前自灰盒掃描上線以來,準確率達到99%以上,基本實現了零誤報,大大降低了運營成本。同時也和傳統漏洞掃描進行了互補,通過將灰盒掃描嵌入到上線前安全測試階段,可以把大多數高危漏洞扼殺在上線前階段。


TAG:EnsecTeam |