利用靜態(tài)分析加固開源入侵檢測系統(tǒng)(IDS)的最佳實(shí)踐
譯文【51CTO.com快譯】入侵分析人員使用網(wǎng)絡(luò)安全監(jiān)控(NSM)的原理來保護(hù)計(jì)算機(jī)系統(tǒng)。NSM是“在對(duì)各種入侵進(jìn)行檢測和響應(yīng)過程中,所涉及到的標(biāo)識(shí)和警告環(huán)節(jié)的收集、分析和問題升級(jí)”。NSM的核心功能包括入侵檢測系統(tǒng)(IDS),基于網(wǎng)絡(luò)的IDS(NIDS),主機(jī)入侵檢測系統(tǒng)(HIDS)和物理入侵檢測系統(tǒng)(物理IDS)。分析人員在部署之前應(yīng)當(dāng)評(píng)估諸如IDS和HIDS的軟件包。
我們可以用許多不同的方法來評(píng)估給定軟件包的安全水平,而其中的一種是使用Aberlarde安全系統(tǒng)工程法。這種方法在軟件開發(fā)生命周期(SDLC)的各個(gè)階段詳細(xì)評(píng)估了商業(yè)和開源軟件包的安全特性。檢查開源軟件的優(yōu)勢在于可以直接訪問它們的代碼。通過這種直接訪問的方式,開發(fā)人員可以使用諸如代碼檢查和靜態(tài)代碼分析的各種技術(shù)。
靜態(tài)代碼分析(SCA)是在不執(zhí)行軟件本身的情況下找出代碼問題的一種方式。為實(shí)現(xiàn)這一目的,SCA的相關(guān)工具通過使用各種可能的輸入數(shù)據(jù),來模擬代碼執(zhí)行的不同分支可能性。SCA工具能兼顧發(fā)現(xiàn)質(zhì)量方面
(如COPY_PASTE_ERROR,F(xiàn)ORWARD_NULL,INCOMPATIBLE_CAST)和安全方面(如UNINIT,BUFFER_SIZE和USE_AFTER_FREE)的問題。SCA工具同時(shí)也能提供一些特定的修復(fù),以便開發(fā)者應(yīng)用到源代碼上,來減少軟件的缺陷密度。它是通過組件的大小(通常是代碼的指定行數(shù))除以缺陷的數(shù)量,來計(jì)算出缺陷的密度。在2014年,開源軟件的平均缺陷密度為0.61每一千行代碼或稱KLOC。相比之下,商業(yè)軟件的缺陷密度則為0.76每KLOC。
我們有許多源自O(shè)WASP的靜態(tài)分析工具可供選擇。自從Coverity掃描服務(wù)面世以來,該公司在過去的十年里備受矚目。開源開發(fā)者們免費(fèi)將他們的代碼提交到Coverity基于云服務(wù)的掃描服務(wù)上,以進(jìn)行分析和檢查。Coverity還可在客戶的本地環(huán)境中部署一個(gè)商業(yè)產(chǎn)品,以提供各種相同的分析工具。本文將介紹Coverity的靜態(tài)代碼分析是如何在不同部署場景中,被用來掃描那些組成安全洋蔥(Security Onion)發(fā)行版的軟件包。
安全洋蔥
安全洋蔥是由Doug Burks維護(hù)的一個(gè)Linux發(fā)行版,其中包括完整的數(shù)據(jù)包捕獲、NIDS、HIDS和一整套分析工具。這些工具包括:
- netsniff-ng:用于全量數(shù)據(jù)包的捕獲
- Snort、Suricata和Bro for NIDS
- OSSEC for HIDS
- Sguil、Squert、Snorby、and ELSA:用于數(shù)據(jù)分析
與單獨(dú)配置每個(gè)工具相比,使用安全洋蔥發(fā)行版可以節(jié)省時(shí)間。在著手使用該發(fā)行版進(jìn)行開發(fā)之前,請(qǐng)遵循Burks 2016來安裝、配置和更新安全洋蔥。一旦完成之后,開發(fā)人員就可以檢查安全洋蔥軟件包的源代碼,以發(fā)現(xiàn)各種安全漏洞。
Coverity掃描
Coverity的首個(gè)部署選項(xiàng)是Coverity掃描。Coverity掃描是一種云服務(wù),也是一個(gè)免費(fèi)的開源社區(qū),已注冊(cè)的開源開發(fā)者可以上傳他們的源代碼用作分析。Coverity的靜態(tài)分析引擎隨即執(zhí)行對(duì)源代碼的分析。之后,開發(fā)們就可查看結(jié)果報(bào)告中的各種問題,并遵循給出的建議來解決問題,然后再重新提交源代碼。
Coverity掃描的實(shí)例:Wireshark
在使用Coverity掃描時(shí),開發(fā)人員一般遵循四個(gè)步驟:構(gòu)建、分析、提交缺陷和審查結(jié)果。在構(gòu)建階段,原始的構(gòu)建命令被作為參數(shù)傳遞給Coverity的命令行:cov-build工具。cov-build的指令運(yùn)用帶有—dir的標(biāo)記,在中間目錄下進(jìn)行原始構(gòu)建和存儲(chǔ)信息。讓我們以Wireshark為例,來看看Coverity的如下編譯命令:
- $ cov-build --encoding UTF-8 \
- --dir ~/cov-inter-wireshark make
在分析階段,中間目錄被手動(dòng)、或一個(gè)連續(xù)集成系統(tǒng)(如 Travis-CI)上傳到Coverity的掃描處。代碼分析是在Coverity服務(wù)器上進(jìn)行的,而并非開發(fā)人員的本地系統(tǒng)之上。Coverity將自動(dòng)處理提交缺陷的階段。通過登錄到Coverity連接的網(wǎng)絡(luò)接口,各個(gè)缺陷將在源代碼的行內(nèi)顯示出審查的結(jié)果。
Wireshark項(xiàng)目擁有著Coverity掃描的一批活躍用戶。自2006年以來,他們修復(fù)了數(shù)以千計(jì)的缺陷。如圖1所示,軟件的缺陷密度非常低,只有0.26每KLOC。
圖1:Coverity掃描:Wireshark (https://scan.coverity.com/projects/wireshark)
Coverity的本地分析
相對(duì)于Coverity掃描的云服務(wù)而言,開發(fā)者也可以選擇購買Coverity的商用產(chǎn)品。商業(yè)產(chǎn)品可以本地模式運(yùn)行在他們的網(wǎng)絡(luò)之中。一個(gè)標(biāo)準(zhǔn)的Coverity部署要用到兩臺(tái)機(jī)器,來構(gòu)成客戶機(jī)/服務(wù)器架構(gòu)。
安全洋蔥一般作為本地開發(fā)主機(jī),以客戶端的方式將其結(jié)果發(fā)送到Coverity的數(shù)據(jù)庫服務(wù)器上。默認(rèn)情況下,安全洋蔥的軟件包以可執(zhí)行文件的形式被安裝。開發(fā)人員必須事先下載它,然后編譯并分析相應(yīng)的源代碼。
開發(fā)人員在客戶端主機(jī)上執(zhí)行代碼分析,而并非使用Coverity掃描的服務(wù)器。存儲(chǔ)結(jié)果的數(shù)據(jù)庫是在本地網(wǎng)絡(luò)上,而不是在Coverity掃描的服務(wù)器上。如圖2所示,通過登錄到Coverity的Web服務(wù)器,并選擇合適的項(xiàng)目(如 Wireshark),便可瀏覽到各種不同的結(jié)果。
圖2:Coverity的項(xiàng)目菜單
如圖3所示,一旦選中了某個(gè)項(xiàng)目,就可以繼續(xù)選擇Coverity的菜單(三道線的圖標(biāo)),并選擇“高危安全風(fēng)險(xiǎn)”。
圖3:高危安全風(fēng)險(xiǎn)過濾器
這張圖將所有的Coverity缺陷過濾到了一個(gè)僅包括安全問題的較小列表之中。
修復(fù)安全漏洞
在修復(fù)代碼之前,讓我們來看看如何對(duì)軟件使用“不傷害原則(do no harm rule)”,以及如何將編譯器的警告納入靜態(tài)分析的體系中。
不傷害
“學(xué)寫整潔的代碼并不容易”。在開始的時(shí)候,源代碼可能是整潔的,但隨著時(shí)間的推移它會(huì)變得“越來越雜亂”。對(duì)于一個(gè)優(yōu)秀的開發(fā)人員而言,既要會(huì)寫源代碼,也要會(huì)閱讀。如果不熟悉代碼的讀與寫,入侵分析人員將會(huì)面臨艱巨的挑戰(zhàn)。
“我們可以將美國童子軍的一個(gè)簡單規(guī)則運(yùn)用到我們的專業(yè)領(lǐng)域:在離開營地時(shí),將其打掃得比你發(fā)現(xiàn)它時(shí)更干凈。如果我們都能在代碼簽入時(shí),使其比被簽出時(shí)更加整潔,那么代碼就會(huì)不朽了。”。
“不傷害原則”有兩個(gè)好處:開發(fā)人員能提高自己的編程技巧,同時(shí)原創(chuàng)作者也會(huì)認(rèn)可開發(fā)者是負(fù)責(zé)任的披露(responsible disclosure)。
編譯器的警告
靜態(tài)代碼分析的另一個(gè)方面是編譯器的警告。人們常重視代碼是否能編譯通過,而忽視了編譯器的各種警告。我們以daq-2.0.6程序包為例,文件daq_afpacket.c的第859行聲明了一個(gè)變量rc:
- int rc
第866行包含了:
- rc = send(instance->peer->fd, NULL, 0, 0)
而編譯器的警告是:
- daq_afpacket.c:859:25: warning: variable ‘rc’ set but not used
- [-Wunused-but-set-variable] int rc
編譯器會(huì)告知開發(fā)人員:來自調(diào)用函數(shù)send()的返回值設(shè)置了變量rc,但是rc并沒有在后面的函數(shù)中被使用到。因此一種解決方案是:刪除第859行,并將第866行改為:
- (void) send(instance->peer->fd, NULL, 0, 0)
這種修改屏蔽了編譯器警告,并盡可能地貼近原始代碼。通過將send()的返回值調(diào)用分配給(void),目前的代碼就會(huì)忽略它了。另一種可能性的解決辦法是:在第866行后,添加額外的代碼,以檢查rc所有的返回值。這樣修改了程序的執(zhí)行,因此需要由維護(hù)人員進(jìn)行審查。
編譯器也具有“視警告為錯(cuò)誤”的能力。如果開啟了此功能,則會(huì)有益于在分階段的項(xiàng)目中引入編碼的規(guī)則。開發(fā)人員能夠一次只開啟一個(gè)警告,逐個(gè)修復(fù),之后在時(shí)間允許的情況下,再打開額外的警告。比如:在Adobe Photoshop中,編譯器就具有開啟“視警告為錯(cuò)誤”的選項(xiàng),以使開發(fā)團(tuán)隊(duì)提高整體發(fā)現(xiàn)能力。如果在構(gòu)建系統(tǒng)時(shí)連續(xù)出現(xiàn)新的編譯器警告,并有構(gòu)建的失敗,那么團(tuán)隊(duì)就能迅速發(fā)現(xiàn)這些錯(cuò)誤。開啟“視警告為錯(cuò)誤”的另一個(gè)原因是:盡量減少各種靜態(tài)分析的缺陷,從而在添加其他工具之前,通過編譯器的幫助,更好地在代碼層面上消除那些缺陷。
Coverity的各種安全檢查
Coverity的7.7版本有著七十多種適用于C和C++的檢查,其中有十八項(xiàng)是注重安全問題的。本節(jié)將重點(diǎn)介紹UNINIT,BUFFER_SIZE和USE_AFTER_FREE。
1. UNINIT
在ANSI C語言中,“變量的初始內(nèi)容是不確定的”。由于該語言允許各種變量在定義時(shí)不被初始化,因此經(jīng)常有大量的沒有顯式初始化的變量在C語言代碼中。一些代碼在變量聲明之后被立即賦值,因此完成了初始化。而有時(shí),編譯器會(huì)自動(dòng)將變量賦值為零。因此開發(fā)人員必須記住這些規(guī)則,這也就給軟件編程留下了安全隱患。雖然已有針對(duì)C語言該問題的解決辦法,但現(xiàn)如今,對(duì)于開發(fā)者來說還是需要記住這些規(guī)則的。
消除這些問題的一種方法是使用Coverity的安全檢查--UNINIT。UNINIT查找未初始化的堆棧變量,以及在堆上被動(dòng)態(tài)分配的、可能會(huì)導(dǎo)致崩潰或安全問題的內(nèi)存。在文件sf_bpf_filter.c的第222行中,daq-2.0.6程序包聲明了一個(gè)int32類型的、名為MEM的數(shù)組。
圖4:mem的聲明
第406行在未初始化的條件下使用mem。
圖5:mem的分配
如圖中的綠色代碼所示,Coverity通過循環(huán)執(zhí)行所有的代碼路徑來仿真運(yùn)行。仿真發(fā)現(xiàn)了:在至少一種條件下,變量MEM在初始化之前被分配給了變量A。要解決此問題,需明確地將如下第222行的數(shù)組進(jìn)行全零式的初始化。
- int32 mem[BPF_MEMWORDS] = {0}
2. BUFFER_SIZE
Michael Howard和David LeBlanc在《編寫安全代碼》一書中提到:“一個(gè)緩沖區(qū)溢出缺陷所需要的對(duì)應(yīng)安全補(bǔ)丁的成本,有時(shí)會(huì)高達(dá)$100,000”。Coverity的安全檢查--BUFFER_SIZE能夠幫助開發(fā)人員找到,并修復(fù)他們C/C++代碼里所包含的各種緩沖區(qū)缺陷。我們以snort-2.9.8.0程序包為例,文件encode.c的第962行初始化了PROTO_ID的各種可能變量類型,直到PROTO_MAX。PROTO_MAX是PROTO_ID枚舉定義的最后一個(gè)元素:
- typedef enum {
- PROTO_TCP
- PROTO_UDP
- ..............
- PROTO_MAX } PROTO_ID;
如圖6所示,第960行定義了功能函數(shù)UDP_Encode。
圖6:越界讀取的示例
綠色的代碼顯示了Coverity所用到的執(zhí)行路徑。從NextEncoder函數(shù)返回的值被存放在PROTO_ID的下一個(gè)類型中。因此存在著如下的情況:其返回的值可能是PROTO_MAX、或22,這是枚舉的最后一個(gè)元素。因?yàn)閿?shù)組的索引始于0而不是1,第992行所指定的下一個(gè)位置雖然超越了數(shù)組末尾,但是會(huì)被索引到編碼器數(shù)組之中。為了防止這種緩沖區(qū)溢出的可能,在它被索引到編碼器數(shù)組之前,我們可以用if/else語句將第992行“卷回來”,以檢查其下一個(gè)是否仍然小于PROTO_MAX。
3. USE_AFTER_FREE
定義各種變量時(shí),一般為它們?cè)趦?nèi)存中保留一個(gè)位置。當(dāng)程序明確地應(yīng)該釋放內(nèi)存時(shí),開發(fā)人員需要確保被釋放的內(nèi)存不會(huì)再有被使用的可能。以不規(guī)范的方式使用內(nèi)存,可能會(huì)導(dǎo)致不可預(yù)測的結(jié)果,和被利用的可能。
消除這些問題的一種方法是使用Coverity的安全檢查--USE_AFTER_FREE。我們以netsniff-ng-0.6.0程序包為例,在文件curvetun_client.c的第304行中,聲明一個(gè)指針去指向一個(gè)稱為“前導(dǎo)(ahead)”的數(shù)據(jù)結(jié)構(gòu)。如圖7所示。
圖7:netsniff-ng – 前導(dǎo)聲明
如圖8所示,第339行將前導(dǎo)指針分配給ai。
圖8:前導(dǎo)指針賦值
Coverity在第358行發(fā)現(xiàn)前導(dǎo)指針已被釋放。第367行的goto語句將程序的執(zhí)行跳轉(zhuǎn)到第311行。下一次通過在第339行的循環(huán),指針在未被事先檢查為NULL的情況下,被分配給了ai。要解決此問題,應(yīng)當(dāng)添加以下代碼到第358行之后,將指針設(shè)置為NULL。
- ahead = NULL
負(fù)責(zé)任的披露
修復(fù)了各種漏洞之后,開發(fā)人員有責(zé)任向維護(hù)人員披露其程序代碼。對(duì)于像使用到GitHub的Wireshark之類的項(xiàng)目,各種修復(fù)項(xiàng)目文檔(如Wireshark開發(fā)者指南,2014),被以“git push”的命令予以提交。其他的項(xiàng)目也會(huì)有郵件列表,或缺陷跟蹤系統(tǒng)用于各種修復(fù)的提交。
未來的工作
在2016年1月,Coverity發(fā)布了靜態(tài)分析工具的8.0版本,其中一個(gè)主要的新功能是具有分析Python代碼的能力。安全洋蔥包含一種被稱為Scapy的數(shù)據(jù)包處理工具。Scapy正在被日益普及,尤其是在構(gòu)建物聯(lián)網(wǎng)時(shí),可被用來分析入侵和調(diào)查各種設(shè)備。未來的項(xiàng)目還會(huì)去檢查Scapy的靜態(tài)代碼分析結(jié)果。
結(jié)論
利用開源IDS加固計(jì)算機(jī)網(wǎng)絡(luò),需要入侵分析人員了解系統(tǒng)里各種軟件包的安全特性。通過針對(duì)IDS的軟件靜態(tài)代碼分析,分析人員會(huì)對(duì)開源軟件所提供的安全特性更為了解。
id Software公司的聯(lián)合創(chuàng)始人John Carmack曾說:“作為一個(gè)程序員,近年來我所做的最重要的事情就是:積極地推進(jìn)了靜態(tài)代碼分析。”這就是這位最有名的軟件開發(fā)者給大家的有關(guān)入侵分析的最佳實(shí)踐。
原文標(biāo)題:Using Static Analysis to Harden Open Source Intrusion Detection Systems (IDS),作者::Jeff Sass
【51CTO譯稿,合作站點(diǎn)轉(zhuǎn)載請(qǐng)注明原文譯者和出處為51CTO.com】