源代碼是構成信息系統的基石。對源代碼進行安全審計,可以在系統實現階段就發現大量的安全漏洞和潛在威脅隱患,可節省5%-20%的后期安全維護費用,減少10%-50%的系統安全漏洞。?
從技術上分析,大中型政企機構的信息系統,往往規模龐大,涉及大量定制開發、外包開發、開源軟件集成、第三方組件使用等,并且開發人員水平參差不齊、開發管理任務復雜、開發語言種類繁多,對信息系統的安全性帶來較多挑戰。通過源碼安全審計,可以及早發現各類安全漏洞和威脅隱患,節省大量安全建設成本及軟件運維成本。
《中華人民共和國網絡安全法》正式頒布實施以來,國家對包括政企機構的網絡安全監管日益完善,不管是網絡安全等級保護制度還是行業監管制度,都對信息系統開發管理、源碼安全審 計做出了規范要求,源碼安全審計作為信息系統安全建設的重要組成部分,日益受到大中型政企機構的重視,在安全建設過程中, 對源碼進行安全審計的機構比例不斷提高。?
靜態分析:不運行代碼的方式下,驗證代碼是否滿足規范性,安全性,可靠性,可維護性等指標的一種代碼分析技術。
實現原理:詞法分析,語法分析,控制流,數據流分析等對程序代碼進行掃描,使用數學方法進行分析。
1 技術概述
1.1?基于污點傳播分析的自動化挖掘技術
1.1.1 檢測模型
自動化代碼安全檢測是一種自動化挖掘、發現源代碼漏洞的安全檢測。在人工形式下的代碼審計中,通常關注以下三點:
(1)輸入源
即用戶輸入可進入代碼邏輯的入口,如PHP語言中的GPC,JAVA語言中的HttpServletRequest等等。此外,在對框架開發的工程進行代碼審計的時候,我們還會關注框架自身封裝的一些入口方式,比如SpingMVC中的@RequestParam注解。
(2)凈化操作
凈化操作包括兩個部分:系統自帶的凈化操作函數以及用戶自定義的凈化操作函數。
這些凈化操作函數通??梢葬槍δ撤N漏洞類型將傳入的參數進行一定程度的字符串變化,從而消除某種漏洞的威脅。比如htmlspecialchars函數,被廣泛認為是用于消除XSS漏洞影響的,雖然在某些場景下一樣可以被繞過。
(3)危險函數
危險函數是觸發漏洞的位置,一般按照漏洞類型進行分類別討論。比如SQL注入漏洞的PHP危險函數為mysql_query,調用該方法執行查詢,如果一旦危險參數可控,則會造成SQL注入漏洞。
目前市面上有基于正則表達式和基于語義分析的兩種檢測方式,基于正則表達式的傳統代碼安全掃描方案的缺陷在于其無法很好的“理解”代碼的語義,而是僅僅把代碼文件當作純字符串處理。目前較為成功的靜態掃描商用產品都運用了語義分析、語法分析等程序分析技術,如開源的Rips中,使用的是PHP內置的Tokenizer系列API來獲取token流。在token流的基礎上進行分析,可以通過每個節點的解析器代號對代碼進行一定程度的理解,如T_ABSTRACT表示抽象類、T_AND_EQUAL代表賦值運算符。
除了在token流的基礎上執行代碼分析,市面上越來越多的代碼漏洞掃描器選擇了從抽象語法樹層面入手執行代碼分析,相比冗長的token流,語法樹層面的分析顯得層次分明、容易理解,這對于平臺開發人員來說是個好消息。
通過更加高級的語義、語法分析,我們可以從待分析的代碼中獲取更多的信息,通過這些信息,就能夠大大提高漏洞檢測的準確度,這也是為什么目前絕大多數商用軟件都選擇在語義、語法分析的基礎上執行代碼安全檢測。
1.1.2 系統層次
一個基于程序靜態分析的自動化審計系統大致應該有以下幾個部分:
(1)靜態分析層
靜態分析層負責對代碼文件進行“理解”,完成語義、語法層面的分析,比如生成抽象語法樹、生成程序的控制流圖等基礎的分析結構。同時,針對PHP語言的一些變量類型進行分類抽象,用于后續的數據流分析中。
(2)數據流分析層
數據流分析主要負責收集代碼中的變量傳遞流向,同時收集變量的凈化情況。數據流分析是污點傳播分析的基礎,其收集的信息越全面越準確,后續的污點傳播分析才會準確。
(3)污點分析層
污點傳播分析是依照數據流分析過程獲取的一系列程序信息來進行漏洞判定的模塊。
(4)其他分析層
其他分析包含針對PHP多個代碼文件的聯合檢測、結合程序的上下文信息進行更加細化的漏洞判定分析、過程內和過程間分析等等。
1.1.2.1?語義語法分析
抽象語法樹(Abstract syntaxtree, AST)是將源代碼按照一定的語法結構,并且將一些冗余的細節抽象為樹狀結構進行表示。市面上有很多語義語法分析工具,如ANTLR,Yacc,Lex,PHP-Parser等等。
這里著重介紹一下PHP-Parser(https://github.com/nikic/PHP-Parser),作為PHP語言的語法分析工具,其本身就是使用PHP語言進行實現的,且支持節點遍歷等功能。
1.1.2.2 控制流程分析
僅僅在抽象語法樹的基礎上進行分析是不夠的,因為抽象語法樹無法很好的獲取程序中流程控制語句的信息。因此需要在抽象語法樹的基礎上構建控制流程圖,將PHP中的分支跳轉進行整理,以基本塊的形式編排抽象語法樹的節點。
在程序分析的時候,必須要對每個分支進行獨立的分析。生成新基本塊的依據是一些控制流程語句,這些語法結構都是固定,大致有以下幾種:
(1)條件分支
If語句、Switch語句、Try catch塊、三目運算符、Or語句
(2)循環結構
For語句、Foreach語句、While語句、Do…While語句
(3)終止結構
Break、Continue、Throw
(4)返回結構
Return
生成控制流圖的方法是當遇到條件分支和循環結構時,需要生成一個新的基本塊,將這些代碼塊中的語法樹節點插入進去;當遇到終止結構時,停止當前基本塊的生成;當遇到返回語句時,停止圖的構建。
1.1.2.3?抽象內置函數
在任何代碼中,都會有PHP內置函數的調用,如果不對這些內置函數進行處理,或者說自動化分析不“理解”這些函數的含義和效果,可能會造成大量的誤報,由于PHP內置函數眾多,每個函數都人工進行處理是不現實的,最好的方式是對這些內置函數進行分類。按照功能和對程序分析的影響,大致可分為:
(1)返回常量值
比如strlen()或者md5(),一旦參數進入這些內置函數將會返回常量類型的結果,比如返回純數字、布爾值、純字符串,這種處理需要考慮為針對參數的一種凈化方法。
(2)返回參數的一部分
某些內置函數如trim() 或者 array_keys() 會將參數的一部分或者全部進行返回。針對返回值,該分類可以分為返回array、返回array的單個元素、返回string類型。
(3)凈化操作
針對某種漏洞類型進行參數凈化操作的內置函數,比較典型的有addslashes,htmlspecialchars等函數。
(4)字符串切割變換
比如內置函數substr()或者chunk_split(),這些內置函數會返回傳入參數的一個子串。由于這種操作會破壞參數原有的結構,如果不追求絕對精確分析的話,實際上完全可以將這類函數的調用視為某種程度的凈化處理。
(5)編碼和解碼函數
編碼函數
如urlencode()或者base64_encode()等執行某種編碼操作的內置函數。
解碼函數
如urldecode()或者base64_decode()等執行某種解碼操作的內置函數。注意,如果在分析中遇到了解碼函數,通常認為該函數調用前參數所受到的一切凈化都是無效的,如果直接進入危險函數就會觸發漏洞。
(6)回調函數
一些內置函數會執行參數指定的其他函數,比如array_walk(),array_map(), set_error_handler()。如果標識其他函數名的參數可以直接獲取到,那么將該調用信息進行進一步分析,
①獲取文件句柄
一些文件操作類型的危險函數的文件參數是以resource傳遞進來的,因此如果僅僅去判斷是否受污染是不可行的,所以需要對獲取文件句柄的函數進行分類。
②白名單機制
在條件分支中遇到這些函數如in_array,array_key_exists等函數,可以默認是一種基于白名單的凈化處理。
③正則表達式校驗
比如preg_match和ereg函數,這里不可能去分析具體的正則表達式,因此為了減少誤報,可以認為調用這些函數進行校驗都是有效的過濾。當然如果選擇犧牲誤報而減少漏報,也可以認為這些函數的調用是不起作用的,可以直接報警。
④其他函數
其他不屬于上述類別但是需要額外分析的內置函數。
1.1.2.4 數據流分析
數據流分析層所做的工作是收集程序中的變量值傳遞、特殊函數調用等信息。可以理解為數據流分析就是一個綜合的程序信息搜集,目的是為了給后續的污點分析提供詳細的參考信息。
對于數據流向的識別和搜集,應該考慮到以下兩種情況:
(1)針對代碼中的賦值語句
(2)PHP內置函數導致的隱藏數據流,比如調用list、func_get_args等函數。
1.1.2.5 污點傳播分析
污點分析過程是在程序分析中,一旦發現危險函數的調用則啟動分析。對傳入危險函數的危險參數進行分析,結合數據流分析時該危險參數的一些程序信息,如凈化信息、內置函數處理信息等進行判斷,如果一旦發現該變量可以被用戶控制并且沒有進行有效過濾,則判定為漏洞。
①定義危險函數
首先考慮如何定義危險函數。在人工代碼審計的時候,會按照漏洞的類型著重關注某一批函數,比如mysql_query, file_get_contents, eval等等。然后我們會找到這些函數的某些參數,然后判斷這些參數是否經過程序處理后還是會存在漏洞。因此,污點分析時,只需要關注危險函數的名稱和危險參數的位置即可,配置示例如下:
print ?=> ?array(array(1),$F_SECURING_XSS)
該配置的含義是:print函數是一個可能引發XSS漏洞的危險函數,并且其參數1是一個危險參數。
②定義凈化操作
定義一個參數是否受到了有效凈化是污點分析中比較重要的環節,這關系到后續漏洞判定的準確性。根據人工代碼審計的經驗,可以抽象總結出一個PHP實現的凈化操作可以有以下方式:
(1)使用PHP內置的或者用戶自定義的凈化函數執行凈化,比如調用addslashes等函數
(2)使用PHP內置的一些校驗類型的函數,比如類型判斷、正則表達式校驗、字符串切割、回調函數以及編碼解碼等操作。這些操作都會進行一定程度的凈化,如果在實踐中,期望有較高的精確度,則可以認為凡是調用這些函數,就認為是有效的凈化;如果我們期望降低漏報率,則可以忽視這些內置函數影響或者執行更加細致的分析。
(3)使用邏輯判斷進行校驗,如使用了==或者===與某些靜態常量進行了比較操作,則認定為該變量接受了凈化。
③執行污點分析
有了上述基礎,可以很清晰地執行污點分析判斷漏洞了,大致的過程如下:
(1)在執行數據流分析過程中,如果發現了敏感函數的調用,則啟動污點分析。
(2)查詢危險函數配置列表,獲取到需要判斷的危險參數列表。
(3)向上找到連接的基本塊信息,關注一個基本塊內所有的數據流記錄,找到數據流記錄右邊的值,提取出該變量。
(4)如果該變量進入到了內置函數,則按照之前章節中整理的內置函數的作用判斷是否受到了有效的凈化。
(5)當遍歷時找不到基本塊中相關的賦值語句,或者賦值的值為字符串、數字或者布爾值,則停止污點分析。
此外,需要格外注意編碼和解碼的影響,:
(1)一個回溯變量被進行解碼操作后(base64/hex/zlib…),該變量向上的所有凈化操作都可以認為是無效的。
(2)如果一個變量被編碼和解碼多次,則進行抵消分析操作,并由抵消之后的結果進行判斷。
1.1.3 其他分析層
在整個語法樹遍歷、控制流圖生成的過程中,可能還會伴隨著以下幾種情況的細致分析:
(1)過程內、過程間分析
當分析代碼時,會遇到方法內部的變量傳遞與程序信息搜集;或者會遇到一個變量在多個方法之間通過參數進行傳遞。支持過程內、過程間分析的重點是作用域之間的對應和轉換。此外,當我們遇到某個函數中執行了對某個參數的過濾,可以將其加入到凈化函數列表。
(2)多文件分析
執行多文件分析時,很多系統僅僅只是依賴use、include、require等關鍵詞來尋找當前文件所包含的其他文件,但是很多程序實現中,往往會用到autoload技術,或者在某個統一入口執行文件包含的操作。所以我們有很大的幾率會遇到某個文件中調用了一個其他文件的方法,但是在該文件中卻找不到該方法的定義語法結構的情景。
因此多文件分析時,我們最好在分析初始化階段中獲取到所有類定義、方法定義、通用函數定義以及其所在的路徑位置,將這些信息保存在內存中。當我們后續分析時,可以直接操作該內存結構,動態提取需要的方法定義。
2 功能概述
①非本地工具,而是一個平臺
在大型公司,可能有成千上萬的產品線,涉及到的代碼庫數量龐大并且編程語言繁雜,面對這種狀況,本地化工具過于松散,不好管理,接入效率低的缺點,而統一的掃描平臺可以提供一整套的自動化接入方案。統一的源碼安全掃描平臺可以區分多場景的任務類型,提供多種接入方式,可以更高效、自動化地提供安全掃描能力。
②軟件開發關鍵環節,中流砥柱
SDL(security development lifecycle) 是微軟提出的從安全角度指導軟件開發過程的管理模式,源代碼安全掃描是SDL方法中的重要環節。從軟件的開發流程上看,源代碼安全掃描處于軟件開發流程上下游,在軟件上線之前需要通過安全掃描。在實踐中,源代碼安全掃描是以插件的形式,嵌入到軟件研發的流程中,為業務線代碼保駕護航。這將要求平臺必須具有高可用性,高響應及時度和完善的用戶反饋機制。?
③戰略地位,支撐安全紅線
企業有一條安全紅線,業務必須遵守的最基本安全要求。源代碼安全掃描也可以用于在系統上線前發現漏洞與違規的內容,禁止漏洞帶到線上,一定程度保護企業業務安全。
2.1?整體架構
從分層的角度,源碼安全掃描平臺大致分為五層,每一層都有明確的職責劃分。
2.1.1 接口層
接口層是平臺對外輸出能力的入口,主要對接口請求進行合理性校驗,權限校驗,參數校驗,以確保請求是合法的,非惡意的。功能上主要分掃描接口和查詢接口,掃描接口用來接收發起掃描任務,查詢接口用來查詢掃描任務的結果數據。
2.1.2 任務管理層
每一次掃描就是一個任務,任務管理層是整個平臺的中心部分,管理所有任務的掃描狀態。主要包括三個職責:
跟蹤。任務管理層跟蹤和記錄所有任務的掃描狀態。
策略下發。源碼掃描平臺根據業務的實際場景,針對不同任務實施不同的掃描策略,比如根據代碼的變更情況使用緩存策略,根據代碼庫的特性調整超時時間等。
超時監控。每個任務都有一個超時時間,當任務掃描時間超過期望值,任務會結束并返回超時異常。
2.1.3 引擎管理層
引擎管理層是對引擎調用的一層封裝,在企業里面涉及到多種計算機語言,不同語言可能使用不同的源碼分析引擎。引擎管理層向上抽象出掃描接口和報告接口,掃描接口接收掃描數據,下層實現具體的引擎調用策略,報告接口獲取不同引擎的掃描結果,統一生成掃描分析報告數據。
2.1.4 源代碼管理層
源代碼管理層主要關注目標源碼獲取、源碼存儲和源碼安全。
?源碼獲取。源碼獲取主要支持兩種方式,第一是用戶上傳源碼的方式,這種方式需要用戶自己把源碼打包,上傳到web服務器。第二種是和企業級代碼托管平臺打通,源碼管理層根據托管平臺提供的安全協議拉取源碼。
?源碼存儲。源碼存儲主要包括兩個方面,其一是需要掃描的源碼,這部分源碼存儲時間不長,在掃描結束后的一段時間內會被清除;其二是有漏洞的源碼,這部分源碼會被永久存儲,用來后續分析漏洞報告使用。
?源碼安全。源碼安全掃描涉及到業務線的源碼下載與分析,對于企業來說源碼具有最高保密性,需要防止源碼泄露的問題發生。這里我們有兩個層面措施:第一是服務器隔離,所有對源碼操作和源碼存儲都放在指定的服務器上,并且由源碼托管平臺負責,這種物理隔離大大減少平臺自身的管理成本。第二是所有源碼存儲傳輸都采用了加密,這里采用常用的方式,使用AES加密源碼,RSA加密AES的key。
2.1.5 掃描引擎
引擎主要利用靜態代碼分析技術分析源碼中可能存在的漏洞。
2.2?SDL中的實戰
研發工程師(RD)提交代碼,進行源碼安全掃描,編譯,持續集成到部署上線,這一系列步驟是全自動化的過程,而源碼安全掃描嵌入到整個流程中,必定牽扯到各個平臺。
2.2.1 接口可用性
一般平臺的接口可用性要求是四個9,即SLA為99.99%,即使企業內部使用,也要達到99.9%。SLA指標很重要,因為一旦某段時間內接口不可用導致軟件持續集成阻塞,會影響到正常的產品發布的流程,導致產品上線出問題。比如企業內某個產品緊急修復了bug需要上線,但是卻在安全掃描這一步卡住,產品線的同學可能會抓狂了。
為了保障接口的可用性,以及及時發現故障,可做以下策略:
①慢啟動。接口的服務邏輯應該盡可能少的計算和IO訪問,這里將請求的原始數據做一次redis寫入,然后就返回,其他的任務掃描邏輯之后才被后續的邏輯“慢”啟動起來。
②接口監控。監控每個接口每次接收請求到做出響應的時間,在響應時間超出一定范圍(一般與調用平臺約定),則進行郵件告警。
③降級策略。在遇到故障無法在短時間內恢復的情況,啟動降級策略,舍棄一定功能的情況下,保障接口能夠及時返回,防止流程卡住。
2.2.2 “慢”是永恒話題
源碼安全掃描作為軟件集成發布的一個步驟,一定程度上影響軟件發布的時間。如果安全掃描耗時過長,將會大大拖累的整個上線流程,安全部本身也會承受著很大的壓力。
2.2.2.1 此增量非彼增量
或許有人會講,RD大多數時候可能只會修改幾個代碼庫的文件,這樣怎么會有掃描速度慢的問題呢?這樣的想法是基于兩個前提:第一,掃描是單文件;第二,文件與文件之間是沒有關聯的。但事實上,前面漏洞分析技術也有提及,白盒代碼掃描過程其實會對相關聯的多個文件進行掃描,可能修改了一個文件,但是有很多其他的文件引用了這個文件,為了保險起見,所以會把相關聯的文件一并執行掃描。
所以一開始的做法是:不管代碼庫如何變化,都使用全量掃描,而這將導致掃描速度特別“慢”,經常會收到業務線同學的反饋,對運營造成很大的壓力。
2.2.2.2 增量掃描的實踐方法
為了提高掃描的性能,嘗試做了增量掃描策略。
大致思路是,如果我們能夠對本次變更的代碼進行分析,找出當前代碼庫中變更文件引用的文件和被引用的文件,將這些文件進行掃描,最后跟全量合并結果。這樣可以大大減少掃描的文件數,提高掃描速度。
具體做法是,在源碼管理層嵌入增量策略層,利用簡單的源碼分析技術,得出文件的引用流向圖。通過對圖的遍歷獲取到增量涉及到的文件。
2.2.2.3?其他優化策略
除了增量掃描,我們還有如下優化策略:
①無變更文件使用歷史結果。相同代碼庫前后兩次掃描,如果文件沒有任何修改,則認為兩次掃描結果一致。
②基于無關文件變更的緩存策略。一個代碼庫,前后兩次掃描,如果前后兩次變更的文件與安全掃描無關,則復用第一次掃描結果。
③閑時掃描。很多時候掃描慢也可能是資源緊缺有關,一個平臺的訪問量也可能集中在某個時間段,因此,閑時掃描作為一種緩存策略,可以大大提高掃描的速度。