一、引言
基于OpenSSL的心臟出血漏洞被認為是CVE-2014-0160的嚴重問題,OpenSSL被廣泛的應用于SSL和TLS插件上。本文用對心臟出血漏洞的解釋來說明這個漏洞是怎么被利用的。
本文中研究了抗心臟出血漏洞及其相似漏洞的專用工具和技術。我首先通過簡單的測試來分析為什么很多的工具和技術不能發現這些漏洞,這樣可以使我們更能了解到為什么之前的技術不能發現這些漏洞。我還要概括總結要點來減少這些的問題。本文不介紹如何編寫安全軟件,你可以從我的書《Secure Programming for Linux and Unix HOWTO》或是其他的著作中學習到這點,本文中認為您能開發軟件。
我的目的是為了幫助防止類似漏洞的出現,從而提高安全軟件的開發能力。正如Orson Scott Card’s Ender’s Game中的虛幻人物Mazer Rackham所說的“這里沒有老師,只有敵人…只有在敵人那里你才能了解到自己的弱點。”讓我們來了解這些漏洞,然后可以避免相似的漏洞再次出現。
二、為什么這個漏洞不能更早的被發現?
這個OpenSSL漏洞是由一個很熟悉的問題引起的,這個關鍵的問題就是緩沖區讀溢出,由于不正確的輸入導致。這些都是很常見的問題,很多的工具是專門用來查找這方面的問題,會使用很多工具對OpenSSL進行定期檢查。
Kupsch和Miller專門查找了心臟出血漏洞,在這個漏洞被發現之前,使用了很多的方法也沒有發現這個漏洞,雖然很多人和工具都用來查找類似的漏洞。他們進一步認識到“心臟出血漏洞對當前的輔助軟件提出了重大的挑戰,而且我們不知道是否有工具能在這個漏洞被發現之前被使用。”我會強調一些其它的問題和我自己的一些觀點。
2.1 靜態分析
在沒有執行這個程序時的靜態分析。
最常用來尋找漏洞的靜態分析工具是source code weakness analyzers,source code security analyzers, static application security testing,static analysis code scanners 和 code weakness analysis tools。每個源代碼分析工具是通過使用類型匹配的方法來尋找漏洞的。有很多的報告可以評估這些工具。
然而,在之前的時間里使用靜態分析工具沒有發現這個漏洞:
1、Coverity:Coverity沒能在心臟出血漏洞公布之前發現這個漏洞。他們正在通過努力來提高他們的工具的質量,從而在將來能夠發現相似的漏洞,使用了一些有趣的新啟發方法。
2、HP/Fortify: HP/Fortify已經公布了一些心臟出血漏洞的描述,但是我沒有任何的證明能說明他們的靜態分析工具在漏洞公布前就發現了這個漏洞,在漏洞被公布后,他們確實是更改了他們的動態測試軟件包,但是和之前的不同。在專門討論這個問題時,他們沒有證據讓我相信他們的工具真的在心臟出血漏洞被公布之前就發現了這個漏洞。
3、Klocwork: Klocwork在正常的配置下沒有能夠偵測到這個漏洞。
4、Grammatech: Grammatech 的CodeSonar同樣沒有偵測到這個漏洞。他們也是通過實驗來提升能力,以便在以后能夠發現相似的漏洞。
最主要的爭議就是這些工具都不能保證能夠發現所有的漏洞,甚至不能保證發現特定類型的漏洞。很糟糕的是這些術語很混亂,所以我們首先要搞清這些術語的意思。
本文中在分析軟件時,使用的工具不能找到所有的漏洞,但是我找到了這個分析軟件的不足。在以前的文章中,很多人使用unsound來描述那些不能找到所有漏洞的來查找漏洞的工具。例如Bessey等人在分析Coverity的靜態分析工具,并且說:“像PREfix產品,我們也使用unsound。”我們的產品并沒有證明說這是沒有錯誤的,而是盡我們的能力來發現這些問題。這項研究工作中存在很大的爭議,雖然它幾乎已經成為商業軟件和研究項目的實際工具基礎。一個unsound術語會導致混亂,因為人們使用program checkers,它使用術語unsound來代替不同的意思。在一個博客上解釋了為什么相同術語會有兩種相互矛盾意思。“大多數的program checkers來證明定律的程序。特別情況下,主要的目的是在某個方面來證明程序的正確性。一個定律的證明是來證明這個定律的正確與否…人民在程序檢查過程中習慣了使用這種做法,所以他們沒有考慮bug的存在。但是一個bug的發現者不是要證明這個程序是不對的,而是用來證明有bug存在,使得他們報告了發現的bugs,它們就都變成了真正的bug,如果沒有錯誤,將會忽略bug,因為他們不能證明是否正確。”
本文中我使用了NIST SAMATE SATE V Ockham Sound Analysis標準來消除混亂,在NIST SAMATE用語中,工具是不能發現所有漏洞的,從而證明程序是不完備的。下面來說明NIST是怎么區分程序的可靠性和完整性:“一個網站的代碼可能會出問題,一個有bug的網站會有一系列問題,這就是說一些輸入會導致問題。一個沒有bug的網站不會出現一些列問題,就是說它是安全的或是沒有漏洞的…這些可以從一個網站的報告中得知。或者說,一個網站有特殊的問題或是一個網站沒有問題…可靠就意味著每一個發現都是正確的。沒有必要使用工具產生每個網站的報告;這是完整的。”
為什么那么多的源代碼分析工具是不完整的?首先,大多數的程序語言不是很容易能被分析的。其次,大多數的軟件使用靜態分析工具來分析也是很不簡單的。最后,完整的分析工具要求更多的人來應用在程序上。相反,不完整的分析工具能夠立刻應用到程序上。他們成功的使用啟發的方法來鑒定漏洞和在有限的時間里來完善分析。但是,這里會有重要的警告:不完整的代碼分析工具常常會漏掉漏洞。
心臟出血漏洞是一個鮮明的使用不完全的啟發方法不能發現的重要漏洞的例子,不能發現這個重要漏洞的最主要原因就是OpenSSL代碼很復雜;多個層次的間接尋址和超出了工具的分析能力,從而不能發現漏洞。局限性和深層次存在的原因是C, C++和Objective-C都很難使用靜態分析;像指針更難使用靜態管理。這并不是意味著靜態分析工具就是沒用的,靜態分析工具能夠測試軟件在大量的輸入后的表現,工具的啟發原理會限制錯誤報告的出現次數。但是更重要的是不完整的靜態分析工具使用了啟發原理后,在分析大的漏洞時會出現錯誤。
2.2 動態分析
動態方法就是使用特定的輸入來運行一個程序并試著發現漏洞。
動態分析局限性是它不能使用時間表來測試任何程序。如在一個瑣碎的程序中加入了64bit的整數,會有2128種可能的輸入,測試這些輸入就要使用13.5十萬億億年的時間。甚至大規模的并行計算也不能解決這個問題。現實中的程序要比這復雜的多,因此動態方法不能體現一個程序的安全性;他們只能顯示在測試中存在漏洞。
但是這并是意味著動態方法就是沒有用的。動態方法在提升安全性上是很有用的,但是要了解到他們的局限。
讓我們來分析我們廣泛使用的兩種方法,但是不能發現心臟出血漏洞: mostly-positive測試和fuzzers。
2.2.1 mostly-positive自動測試套件
一個方法是開發一個強大的自動化測試套件。Eric S. Raymond和一些其他人在研究心臟出血漏洞時,他的表述為:“我認為很多人都認為這個測試套件不能很好的工作…我以前學到了盡量去推進傳統方法來完成你不能達到的程度。”我同意他的觀點,一個好的自動化測試套件是很強大的,特別是應用于非安全性缺陷時。如果你沒有或是開發一個,就完全的停止,我們表示同意。
但是,測試套件能否發現心臟出血漏洞要依靠你是怎么開發的這個套件。很多的開發人員都開發了測試套件,這個套件主要是我說的“mostly-positive”測試套件,它可能不能發現心臟出血漏洞。后面我將會討論negative測試,這種測試方法可能會有作用,但是我們要知道為什么一般的測試方法不能做到。
很多的開發者和組織者專門測試了正確的輸入時會發生什么。當你這樣思考時就更有意義;一般的使用者都會抱怨在正確的輸入時是否會沒有正確的輸出,并且大多數的使用者不會測試在不正確的輸入時程序會有什么結果。如果你的目的是很快的分辨出問題,在大多數的情況下,在正確的輸入時人們會努力控制能夠預測的錯誤條件。很多的開發人員不能思考到當入侵者發送精心設計的輸入來利用程序時會發生什么。
我會使用mostly-positive測試套件的方法來測試在正確的輸入時會發生什么。不幸運的是,在很大程度上,今天的軟件入侵測試套件都是mostly-positive。都在關注開發mostly-positive測試套件的測試。
1、Test-driven development(TTD)是一個軟件開發過程,“開發者寫了一個自動測試工具來提升和增加新的功能,之后使用最少數量的代碼來通過測試,和最終生成新的代碼的接受標準。”通常情況下這些測試是用描述一個新功能要做什么,而不是他們不能做什么。
2、當很多的標準鏈接在一起,交互式測試就會決定他們是否能夠鏈接和分享數據。交互式測試能夠很好的幫助開發者來提高一個協議標準。但是其他的實施方法也能遵守這個規則,其他的實施方法不能夠完成“什么不發生”的測試。
mostly-positive測試實際上對于安全軟件來說是沒有用的。mostly-positive測試一般不能測試正確的事物。對于心臟出血攻擊和其他的攻擊,攻擊者發送數據用不是平時用的格式。TTD和交互式測試都是好工具…但是你需要加強他們來改善安全軟件。
代碼覆蓋工具對漏洞的發現沒有任何的幫助。一個開發者可能會運行代碼覆蓋工具來看下哪些程序沒有被測試,之后增加測試來達到大部分的代碼被測試。當測試套件測試了80%-90%的代碼時,很多人會很高興;一般很少會達到100%的覆蓋。測試覆蓋工具有某種安全價值,例如,他們有時能夠偵測到等待出發者的惡意軟件,他們也能夠核查是否有特別的程序能夠正確的運行。但是使用典型的代碼覆蓋工具測試到100%的覆蓋,不能對抗心臟出血漏洞。心臟出血漏洞缺少恰當的輸入驗證。基本上,一個代碼覆蓋工具不能注意到丟失的代碼;只能注意到沒有測試的代碼。
我要說在OpenSSL里也不會有例外出現,又如在蘋果的iOS設備上運行SSL/TLS得到了錯誤的結果時,也能通過mostly-positive來證實它的測試。在這個漏洞中,SSL/TLS庫接受了有效的證明。然而,沒有人驗證這個庫能拒絕某些無用的驗證。如果你只是測試是否有效的數據會產生有效的結果,你就不能發現安全漏洞,因為大多數的攻擊都是在基于程序沒有準備好的時候輸入。
如果你開發的套件能使用我在下面描述的方法,你能夠通過一個好的測試套件來發現這個漏洞。首先,讓我們來研究fuzzing。
2.2.2 Fuzzers 和fuzz測試
Fuzz測試是一個隨機輸入,之后發送到程序測試去看是否出現不渴望的過程。進行fuzz測試的軟件叫Fuzzers。
Fuzz測試同傳統的測試不同,在傳統的測試過程中,你會有一個給定的輸入組,并且你知道每個輸入對應的輸出情況。傳統測試會隨著測試數據的增加而變得更復雜,因為你要預測渴望的輸出結果。決定輸出想要的輸出結果是一個Oracle機制。使用一個Oracle的數據作為輸入的問題被叫做Oracle problem。
Fuzz測試處理不同的Oracle problem,因為它只是在試圖偵探能使程序崩潰的問題。這就是使它在Fuzz測試中輸入更多的測試數據,即使輸出測試更不準確。Fuzzing測試方法是1988年Barton Miller在University of Wisconsin開發的。在http://pages.cs.wisc.edu/~bart/fuzz/上有更多的有關fuzz測試的信息。
Fuzzers經常用來發現安全漏洞,因為他們能夠測試大量不可想象的輸入。特別是,Fuzzers常用來發現輸入驗證的問題,心臟出血漏洞就是在輸入驗證錯誤的基礎上產生的。但是典型的Fuzzers不能發現心臟出血漏洞,因為:
1、心臟出血漏洞是由于緩沖區讀入溢出,而不是緩沖區寫入溢出的漏洞。大多數的Fuzzers只是發送大量的數據和尋找程序的崩潰。但是,當緩沖區寫入溢出常常會導致崩潰,緩沖區寫入溢出在正常的環境里是不會崩潰的。在Fuzzing過程中,甚至會使用一些對策來解決寫入溢出,而不是讀入溢出,如canary-based保護方法和非執行堆棧。可靠性不是很大的問題,因為存在方法是出入溢出導致崩潰,或使用其他的測試工具來測試…這會給我們帶來第二個問題。
2、OpenSSL包括它的內存分配路徑,并且除非使用特別的調試方法,不然我們會經常使用他們。更糟糕的是這些特別的方法不能正常工作。特別是,OpenSSL使用它自身的應用程序管理緩存來提高性能時,而不是簡單的依靠內存管理路徑。這種應用程序管理緩存可以阻止許多典型的緩存例程,包括測試工具如electric fence、valgrind和address sanitizer。這就意味著使用fuzz測試來測試緩存的路徑問題時,測試會忽略一些特殊的情況。
因為加密技術能在很大程度上減少fuzz測試的無效性,除非fuzzer有密碼和專門用來襲擊的密碼庫。有一些fuzzing不能在OpenSSL和一些密碼庫下嚴格執行的推測應該是正確的,然而,沒有什么能阻止密碼被fuzzers獲取。除此之外,心臟出血漏洞甚至在沒有密碼的情況下被發現。因此,就是在讀出溢出和OpenSSL使用自身的內存緩存分配路徑的聯合使用時,使fuzzing變的無效。
三、用什么來對抗類似心臟出血的漏洞?
這里有部分在先前可以對抗心臟出血漏洞的軟件和工具,我會特別的介紹一些好用并且免費的自由開源、源代碼軟件。
首先是一些說明:
不要只用一種工具或一種技術來開發安全軟件。開發安全軟件要集合很多的方法,最開始要知道怎么開發安全軟件。大多數的組織最初是使用簡潔清晰的方法來開發安全軟件,啟用和注意編輯器的警告標志,使用源代碼弱點分析工具,讓多人進行研究,運行fuzzers,使用大量的自動入侵工具套件。如果你只是使用一種技術,你只能對抗上一次攻擊而不是這次。例如,會大量的忽略掉警告標志,即使是警告標志不能發現心臟出血。那就是說,當攻擊成功時,最重要的是怎么完善軟件,攻擊者可能如使用一樣的方法來入侵軟件。更好的改進也能對抗其他的攻擊。
這不存在一種類型的工具和技術的列表,不同的工具有不同的用途。可以在[BAH2009] [NIST]上了解更多關于各個類型的工具和技術。我創建了這個特別的列表,但是我要盡量清楚我的意思。
先前沒有一個明確和完整的用來發現心臟出血漏洞的工具和技術的列表,我很想做一個,我希望得到更好的建議。
以上是一些說明,先前是什么在對抗這個漏洞的,為了完成這個,我已經大致的完成了這個列表,用最簡單的方式介紹他們。這是最粗略的,會有一些問題;歡迎來改進。使用更多的方法來對抗其他類似漏洞,不只是心臟出血。使用動態和靜態分析法來識別在括號里的副標題。
3.1 使用negative測試(動態分析)
negative測試會得到錯誤的結果。例如,對于一個設置了密碼的系統來說,在知道有效的用戶名和密碼時,要通過很多次的回歸測試才能登錄成果。negative測試會顯示很多無用的用戶名和密碼,其他的無效的輸入會阻止用戶登錄。
通過negative測試方法創建了一系列的使用錯誤輸入的測試。我指的是每個類型的輸入,因為不能測試每一個輸入,在動態測試中能得到解釋。在回歸測試工件中要包含無效數值來測試每一個輸入,每個狀態/協議轉換,每個使用說明書等等。這就會立刻發現心臟出血漏洞,因為心臟出血漏洞包含一個不正確數據的長數值。這也會發現其他類似CVE-2014-1266的錯誤,如在蘋果iOS上使用SSL/TLS會得到的錯誤。在CVE-2014-1266中,iOS存在接受無效認證的問題。很多的測試中存在有效的認證…但是,沒有足夠的測試來測試無效的認證。
在大多數情況下只有negative測試對安全還有點用途,之前,我注意到重要的是如何創建測試套件。對于閱讀本文來說是明顯的,特別是,當我懷疑Eric S. Raymond在討論測試的優勢時使用這些類型的測試。但是這對于軟件的開發者來說是明顯的,大多數的開發者和組織者都是在使用mostly-positive test套件。很多的開發者很難像攻擊者一樣的思考,只是錯誤的通過廣泛的測試不可能發現的原因。
通過negative測試的就是起初的半自動的過程,您可以開發出可以執行計算機可處理的規范,并生成大量測試結果的工具…之后看看是否可以控制它。
另一個使用negative測試的重要條件就是是否有一個標準,可能合作開發一個獨立的普通測試工件作為FLOSS項目。之后可以通過快速測試所有當前和以后要執行的方法,并且阻止使用者遇到問題。我強烈的推薦使用SSL/TLS協議開發一般目的測試工件;不然會減少效果,這會增加應用的安全性。單獨的應用也需要使用附加測試來補充單一測試,但是一般的大測試套件是很有用的。
軟件測試是一個完整的領域。存在著不同類型的測試方法和測試范圍標準。我只能在本文中總結測試。更一般的信息可以看下Paul Ammann and Jeff Offutt 的Introduction to Software Testing。但是要理解這個:只用使用有效的輸入來測試來發現這多的問題,如心臟出血漏洞。
我不知道在整個negative測試中依靠什么,或是其他什么單獨的技術,對于安全來說。動態的方法很自然,在真實輸入空間只能測試一個微不足道的部分。但是這種方法很容易發現安全漏洞。
3.2 地址核對和標準內存分配在fuzzing
不幸運的是一般的fuzz測試方法在這種情況下是不能很好使用,但是我們可以學習簡單的過程。如果可以使fuzzing對一系列的地址進行有效果的、容易的核對和使用。這種類型的工具能偵測到在執行過程中讀溢出和加寫溢出,并且常常發現其他的存儲問題。
許多的工具能夠完成對地址的核對;每一種工具都有兩面性。但是,如果你沒有使用其他的什么工具,我強烈的建議你嘗試下address sanitizer。
address sanitizer簡單有效;它只是一個在LLVM/clang和gcc中建立的附加的標志。address sanitizer沒有什么神奇的;它只是擅長偵測緩沖區的讀寫溢出問題,釋放后使用或是雙重釋放。它也能偵測到use-after-return和存儲泄漏。它不能發現所有的存儲問題,但是這是一個很好的工具。它的表現超出平均的73%,使用2x-4x的存儲。這種表現一般和測試環境是無關,在偵測這些問題時它很少被提到,在測試過程中Chromium和Firefox網頁瀏覽器都使用的address sanitizer。要了解更多可以去看USENIX 2012或是address sanitizer網頁。
還有其他的工具能夠偵測內存的使用和分配地址的問題。很多人使用guard pages來檢測讀和寫在緩存。Valgrind被廣泛使用和廣受歡迎;valgrind在檢測內存時能發現很多的問題包括在堆棧中讀溢出。另一個廣泛使用的工具是electric fence。在運行不同的fuzzer時可以使用不同的工具。
一般情況下,使用fuzz測試時你必須打開你能打開的所有的探測設備。第一個fuzzer偵測時使用這種機制“沒有改變的程序崩潰或是死掉?”并且很多的fuzzer還只能做這個。你必須要加強程序的訪問,并且要開發盡可能多的訪問。你可以增加額外的核對來確保中間和最終程序語句的正確。要不是心臟出血漏洞的出現,你至少應該打開無效的內存訪問探測器,如address sanitizer。
許多的工具包括address sanitizer和基于程序的guard page,要求這些程序具有測試正常分配和釋放內存的能力。特別是,程序沒有必要使用符合分派準測的機制。至少,這個程序應該可以輕松地使用正常分配方法用于fuzz測試。
一個相對的方法是concolic測試,CREST就是一個基于C的concolic測試的自動測試過程工具。但是CREST目前只能用于象征性為線性整數的運算,所以它能在這種情況下工作。更一般的是現在的concolic測試工具不可能發現心臟出血漏洞。如果哪個人說他確認concolic測試發現了心臟出血漏洞,請讓我知道。
大家一直都在為fuzzers是否比negative測試復雜而爭論不休,但是這只是我的推理。negative測試的一個優點是它很容易入手;假設你已經有了一個測試套件,你就可以開始negative測試。更重要的是negative測試能快速給出一個模糊導致問題的答案,他們要求計算能力很少的開發者使用每一種方法來獲得重復測試套件。相反,fuzz測試要求更好的計算能力和對結果的解釋;計算能力不算什么,這會影響到對開發者的反饋速度。一個潛在的更快negative測試的反饋能夠是開發者更快的實現檢測和修復;今天最大的問題就是開發者的時間,而不是計算時間;一個最好的機制能減少開發過程的復雜度。你也可以在某個特定的協議下,使用negative測試套件;你能夠在每個測試設備和實施方法中輕松的重新使用測試套件。當然,這不存在什么沖突;最好使用fuzz測試和negative測試兩種方法。
3.3 編輯內存分配標準和使用address guard或是sanitizer
如果在對抗來自潛在的漏洞攻擊,在未知的環境里你現在就要使用一個程序怎么辦呢?
一個方法就是使用偵測在分配的內存的最后區域來實現讀的機制,但是使用這種方法你不能改變測試怎么運行;這個觀點就是你實際上用的在一個分配要求下的多重分配機制。
存在運行時間偵測漏洞的多種機制;這就是一些例子:
1、Address sanitizer。你要重復調試一個程序時可以使用它。在LLVM/clang和gcc編輯器上Address sanitizer就是一個標志,這相對與C程序的軟件簡單的多,這占了平均運行的73%,和2x-4x的存儲。這不是你想只能手機上做的,很多繁忙的網站不歡迎這些。現在的計算機比過去的有更好的能力和存儲,一些環境中就會被接受…這就是你能夠立刻對抗未知攻擊的可能性。Address sanitizer在偵測一長系列潛在問題上很有作用,包括大量無效的緩沖區訪問。Address sanitize不是在所有的編輯器里都無效;這要在其他的如C, C++, 和Objective-C編輯器中來添加它。
2、Intel Memory Protection Extensions。MPX新增了叫邊界寄存器的寄存器來控制指針的邊界,使用新的指令來運行和使用邊界。MPX使用Skylake架構,但是在2014年這些CPU不能和公眾見面。這要更長的時間被廣泛的得到使用,那不能組成non-Intel系統。
3、內存分配保護頁面。一些系統內存分配能夠在分配一個用來組織讀和寫的內存后,添加一個未定的保護頁面。這些能否會禁止和阻止心臟出血漏洞,這些取決于它是如何實施的。OpenBSD的malloc的實施支持保護界面。在OpenBSD中,G選項會導致“使用保護頁面后的每個頁面分配到的數據大小過大,這些會導致訪問錯誤。”這會與P選項進行組合來移動一個頁面內的分配。OpenBSD機制可以啟動特定的程序,甚至是特定的默認情況下,在整個系統中啟動,這樣就可以在更多的環境下得到保護。OpenBSD的malloc機制有一個弱點:即使開啟G和P兩個啟動項,少量的分配不會立刻完成保護頁面。如果OpenBSD的保護界面機制能夠在較少量的分配后立刻插入一個保護頁面,我認為會更好,即使這可能會對速度和內存大小有很大的影響。但是即使是這樣,開啟G和P就意味著所有大于半頁的分配會立刻跟隨一個保護頁面,并且分配一個半頁或是更少將會泄漏半頁。這就會明顯的減少泄漏規模,相比于原來心臟出血漏洞攻擊時泄露的64K。內存分配必須要對齊,所以保護頁面可能泄漏一些字節的信息,這就取決于如何實施的。我懷疑Address sanitizer要比增加保護頁面的分配快,但是添加保護頁面不要求更多的程序來進行重新編譯,這就是它的優勢。不幸運的是GUN的libc中malloc不能有附加的這些功能。
當然,這種方法假設你有一個能啟動(1)的內存保護機制和(2)內存保護機制也會在這種機制下工作。很多的機制可以對抗緩存寫溢出,不是緩存讀溢出,心臟出血漏洞就利用了讀溢出。例如:GUN的libc中的malloc()可以選擇MALLOC_CHECK_。這就是防止寫溢出的方法,但是我不認為它能對抗類似心臟出血的讀溢出。同樣,Dmalloc’s fence-post檢測“在程序從這個區域中讀取時不能注意到,只有在寫入時才會有通知。”我覺得GUN的libc和一些類似的運行過程中也要增加類似OpenBSD的malloc的保護頁面機制,從而對抗讀溢出。
這是一個可以減少傷害的方法,而不是一個消除個問題的辦法。從安全的角度來看這種方法把缺少保密變成了缺少實用。然而在很多的情況下這是一個很好的協議。一旦受到攻擊,這個方法就會使問題變成可視化的,一旦問題可視化后就變的很好改正了。
這個方法很容易和honeypot或honeynet聯系在一起。在honeypot或honeynet系統上設置這些硬化的方法。如果攻擊者試圖破壞軟件,這個軟件不會崩潰,并且會記錄下攻擊者的重要日志和追蹤記錄。Forensics就會偵測到一些專門利用一日0攻擊。我認為通過一些日志記錄結合入侵偵測系統來進行追蹤;在硬化密碼庫中發生了崩潰,就會特意的記錄下。這就會使普遍的偵測利用一日0攻擊更加的容易。分布核心基礎設施組織和在互聯網上其他組織都可以建立這些類保護我們。
雖然這種方法并不能完全解決這個問題,但是他能提供一個有力的緩解功能。一些發行者或組織可能需要在特定情況下使用這些措施,或至少使這些措施變得更容易。
修改代碼不會很復雜,并且重新編譯也是很簡單的。不過,在很多的設備上性能的欠佳都體現的很顯著,可能是你失去了硬件后的性能。特別是在使用Address sanitizer時,你會失去一半的速度。因此,我指望使用這種復雜的解決方法,就要考慮到硬件的消耗。在很多的情況下,會影響到運行,在智能手機上就會降低運行速度和電池的壽命,對于當前流行的服務器的話,也會減慢反應速度和增加電量的消耗。如果將來的CPU能支持Address sanitizer,對速度的影響就會顯著的降低了。我希望CPU制造商能考慮下這點。
3.4 關注各個領域的手動檢測驗證
漏洞的代碼是人為審查的,顯然只有一個人來審查是不行的。
然而,大量的工作就要有專門的人來檢查每個領域,為確保得到有效的驗證,有時會在計算機安全中得到一個不好的名字。我懷疑的原因之一就是有時候,那些部署清單的人在做什么,之后也不能很好的利用它。但是出色的飛行員經常使用儀表盤,他們知道是做什么的。如果補丁是他們使用清單上工具后的唯一成果,“必須證明每一個不可信的數據字段進行驗證,”之后這個漏洞被反擊。
列入人為檢查/審計的一部分,和一些簡單的方法不同。然而,這確實要就檢測人能了解所有的補丁,它不能依靠以前的代碼來得到幫助。
3.5 對文件包括注解系統的配置源代碼的弱點分析
傳統的源代碼弱點分析是找不到心臟出血漏洞,因為他們使用的是通用的啟發式方法,代碼復雜,在這種情況下不能很好的起到作用。它總是你能看到的最簡潔的代碼,但是基于你要完成的任務總是會有一些復雜性,真實情況下人類是不能達到完美的簡約。Coverity公司正在開發一些新的,他們認為能夠檢測到心臟出血漏洞的啟發方法…并且對他們是有好處的。至少有一個人已經使用了類似的啟發方式。事實上,我希望所有的代碼分析工具都能得到改善,從而發現他們以前不能發現的漏洞。但通用的啟發方式在某個特定的時間點只能達到這個程度,你能做的更好嗎?
回答是肯定的,它叫做為上下文配置的源代碼弱點分析工具。基本思想是,你開始使用一個惡源代碼弱點分析工具,之后你在提供更多的你要分析的程序的信息。這種方法比僅僅運行源代碼弱點分析工具需要更多的時間,而這些額外的信息通常要和一個特定的工具聯系在一起。然而,提供你需要的程序的信息,源代碼弱點分析工具可以能更好的工作。
Klocwork已經表示這種方法對心臟出血漏洞是很有效的。
現在讓我們來談談注釋系統。在很多的地方來為靜態分析工具提供這種額外的信息。一個常用的方法就是對程序添加額外的注釋機制,在修改程序時會使用他們。這些注解可能在更改的代碼中進行添加,添加在注釋中,或是加在單獨的文件里。使用C的工具或是注解包括Microsoft’s SAL、splint、Deputy、Oink/CQual++、cqual、和Frama-C ANSI/ISO C。你可以很容易得出添加這些信息確實是一個不同的技術。
認真的使用這些額外的注解來對抗漏洞就要有很大的工作量,如果從現存的代碼來說。對于C來說存在許多不同的不兼容的注釋系統。對于他們來說是沒有什么標準的,這會進一步的阻礙他們的使用。畢竟,它需要添加注釋和這些注釋會把你鎖到一個特定的工具中;Microsoft SAL會有更多的問題,沒有FLOSS的應用和這只能在Windows上使用。我認為如果針對每個主要的編程語言包括C在內,任何一種單一被廣泛接受的標準注釋符號,注釋系統將會更加廣泛的應用。當沒有這么個符號時,像C語言等語言就會很難得到那樣一個協議。Peter Gutmann已經寫了一些他的經歷。
但是,注解系統是由一些好處的,注解系統能夠發現簡單的漏洞,不用在轉變成不同的語言。他們也很少去轉變成不同的語言,當然,這并不沖突;你可以切換語言,使用一種新的語言在注釋系統中。
3.6 實現100%的分支覆蓋率
或許有另一種方法可以發現心臟出血漏洞,實現100%的分支覆蓋率。如前面所述,在一個缺失特定程序的中輸入有效的驗證碼時,分支測試是不檢測到的。但是分支覆蓋可以在不同的實現方法中檢測未經驗證的分支程序。努力實現一個測試套件,讓多個實現全覆蓋分支大大增加了丟失了驗證碼和遺漏了異常處理時被檢測到的可能性。更強的測試覆蓋措施也會工作的很好,如修改條件/判定語句。
這個測試套件必須要包含多個應用,實現100%的分支覆蓋。更重要的是,有不同的實現方式,效果更好。最終,用一個特別的方法來發現漏洞。此外,這種方法比其他的方法更難發現安全漏洞。可能是因為在相同的路徑下輸入不同的數值,但是只有小部分可以引起問題。如果在測試中其他的一種方法實施了特定的組件和實現了潛在的缺失驗證碼的代碼,它也只能有作用。我從來沒有在其他的文獻中見過這個特定的方法;人們通常討論一個執行分支的覆蓋。不過,會注意到這種方法不僅可以提高能力,也能發現特殊的漏洞。
實現100%的分支覆蓋率比徹底的negative測試更加復雜,這是因為如果你有個很差的測試套件,它會花費大量的時間來從一個錯誤的分支轉向,來弄清楚怎么激發它。錯過的分支往往是很難觸發的在特定錯誤的處理系統中,或是對無法驗證“不會發生”的分支作防御性設計。此外,這個套件變得更強大能夠實現100%的覆蓋;很多的組織不能嘗試增加一個單一的方法來使分支覆蓋到100%,不關心100%的分支覆蓋。
這就存在一個問題:這不能很好的反擊心臟出血漏洞,因為在很大程度上取決于所有的配置擴展或是注解以及怎么使用。在另一方面,它們不取決于完全沖擊的正確輸入;靜態分析工具可以同時檢測大量的問題。
3.7 攻擊運行認定
軟件開發人員積極的插入和開啟運行認定。有人猜測這是對心臟出血的反擊,所以我將在這里研究下這中可能性。
軟件開發人員可以斷言各種價值關系和狀態必須是正確的。這些斷言可以在運行時停留。幾乎所有的語言都會有一種內置的判斷機制,有些語言會有一些內置的先進機制的前置條件,后置條件和不變量。在某些情況下,這些語言可以優化一些判句,會留下一些在優化過程中不能優化的問題,一個注解系統可以用靜態來實現,一部分可以用動態實現;我先前對注釋系統的靜態應用的評論。
暫時增強系統邏輯斷言是一個更為先進的使用暫時斷句的研究方法。你可以在http://www.cl.cam. ac.uk/research/security/ctsrd/tesla/.網站上發現更多的信息。
不用懷疑斷句可以有一個極好的機制來用于檢測無效狀態,無效狀態有時是一個最弱的指標。
然而,這中方法在涉及到對抗心臟出血漏洞時確實有些不足。不論是原開發商或是檢查的人意識到檢查請求報文的長度值是很重要的;因為沒有使用長度檢查,開發者是不能添加判據來檢查它。這是一個在進行negative測試時的問題,但是negative測試可以通過分割這些要開放的功能的代碼來簡單的實現,很容易證明所有的數據字段都在進行檢查,所以我感覺negative測試會更有可能發現存在漏洞的類型。因此,雖然積極的注釋可以很有效的對抗漏洞,在某種程度上它會在特定的情況下工作。
我把這種方法看作是一個較為復雜的選擇。使用這種方法可以檢測到心臟出血漏洞,需要積極使用判據。增加這些判據要使用大量的開發時間和提高運行的成本。
3.8 更安全的語言
心臟出血漏洞產生的原因是C語言不包含有任何的內部檢測或是方法來對抗緩沖區不當的限制。不恰當的限制會導致災難性的問題,所以幾乎所有其他的編程語言都會自動對抗不正當的限制。
如果在一個給定的程序中的漏洞可以造成災難性的影響,那么選擇它的程序語言時更應該減少漏洞存在的可能性。越是災難性的影響,就越要有更好的表現。大多數的程序語言提供對其他危險漏洞的保護措施,如不恰當的限制保護。某些程序語言有更小的可能性出現被誤用和不正確使用的結構。理想情況下,一種語言將會阻止所有漏洞。通用的語言都不能阻止所有的漏洞,但是它是編程者爭取的一個目標。沒有“絕對安全”的程序語言;它是一個繼續發展的事物,一些語言提供了更多的對策。
3.8.1 危險語言和為什么使用他們
最廣泛使用的與安全有關的軟件有C,C + +和Objective-C。所有的這些語言都沒有提供緩沖區的訪問限制,實際上,它要通過努力來限制緩沖區讀和寫溢出的出現。對緩沖區訪問的不恰當的限制會被廣泛使用類型的災難性的影響漏洞。使用或是轉變成其他的語言將會消除緩沖區的漏洞,包括心臟出血漏洞。C語言更是這樣,因為它缺乏很多可以避免緩沖區出現問題的高級結構。大多數語言也可以防止內存釋放錯誤,可能會導致安全漏洞,以及一些語言也會被設計成對抗其他漏洞。其中在現在系統中有很多漏洞的原因之一是C、C++、和Objective-C語言的過度使用。實際上,有人提出禁止在安全性敏感的代碼中使用這些語言。
C、C++、和Objective-C語言的廣泛使用是有原因的。在TIOBE編程區域指數來衡量的編程語言的流行,2014年4月占到了使用人數的前四。這些原因包括更高的性能和界面簡單,大型的存儲,更好的表現,熟悉性。此外,把語言轉變成大型程序是需要很大的努力。讓我們來看看原因。
3.8.2 替代產品的運行速度和內存性能
一個經常被引用的問題是使用C,C++,和Objective-C比其他的程序的運行速度快。此外,當要和硬件連接時,其他語言就會缺乏最底層的機制。如果你要很快的速度,你可以直接連接,語言列表中的運行時間會更短。運行速度直接決定著移動設備和服務器領域。基準游戲中速度分析程序是使用不同的語言編寫的。“程序語言編程的大致等級”中說到,發送數據和不同語言的包是根據他們的大致速度來實現。沒有完美的基準,它始終是一個最好的衡量績效的具體方法。不過,我更喜歡數字的大致猜測,這個數據即足夠代表開始。如果性能是你要追求的,你不想使用匯編語言,可以考慮下下面的分析:
Fortran。Fortran的應用表現經常要比其他的語言好,尤其是在數值計算上。但是,我不清楚很多人會把很多的代碼轉變成更原始的編程語言。特別是據我所知,即使是現代Fortran也沒有和低級的硬件的接口的標準機制。
Ada。Ada用于真實時間系統的應用,因此,它會有更好的性能,包括訪問底層的組件。Ada可以用于對抗錯誤,在編譯時表現的最好,它的語法是專門用于對付錯誤的,Ada一定能對抗緩沖區的心臟出血漏洞。很多人都不喜歡像Ada一樣的語言,因為Ada要很嚴格的靜態類型檢查,但是這中檢查是發現缺陷的關鍵機制之一。Ada一般廣泛的應用在像航空鐵路等這些高保障的地方。
ATS。這不是一種眾所周知的廣泛使用的編程語言,但是它確實非常成功的應用在特定的基準測試套件上。我要指出ATS不在最近使用的程序列表上。
還有很多其他的編程語言,特別當你愿意放棄由基準確定的速度時。例如Go的性能就很好。Rust是另一種你可以考慮的程序語言。Java在當前的JITs上有很合理的表現,一旦它運行起來,但是這有個一個特別的啟動時間。其他的語言在基準下也很有用,如Scala,Free Pascal,Lisp SBCL,Haskell, C# on Mono, F# on Mono, and OCaml。不論是D編程語言還是Nimrod語言都列出了相應的標桿。但是使用他們時也要考慮到效率問題。
當然,如果速度不是關鍵,很多的軟件都能被使用。一個研究表明,用.NET, Java, ASP, PHP, Cold Fusion和Perl來編寫的程序中的靜態漏洞沒有統計學上差異。所有這語言要比C,C++或是Objective-C安全。因此所有人都可以防止緩存溢出的問題。
這有太多的編程語言可以使用,我就在這多說了點。我的目的不是列出說所有可以替代的編程語言,而是讓人們知道是有替代的存在。
性能不單單是速度,還有內存的管理。在移動設備上尤為重要。C, C++和Objective-C沒有自動垃圾收集器,但是其他的語言有這個功能。開發人員如果不考慮內存管理時,他們考慮的是效率,但是在很多的環境下是不現實的。Drew Crawford對移動設備的發展做了很長時間的研究,他指出“如果你需要至少6倍的內存,自動垃圾收集工作會很管用,但是如果這里少于4倍的內存,會減少效率。”iOS基于人工操作的大多數事情的文化,并且試圖讓編譯器做一些簡單的東西。Android基于他們努力不在實踐中應用垃圾收集器工作,但是不論哪種方式,當他們編寫移動應用程序時,每個人都花了很多時間考慮內存管理。內存是不可以替代的。OS X Mountain Lion v10.8中廢棄了自動垃圾收集器,在以后的版本中會把它刪除。都推薦使用自動引用算法。不是像OS X和iOS一樣。這就是人們選擇C, C++,和Objective-C的原因。
C, C++, 和 Objective-C編寫的程序比其他的語言的運行性能好嗎?回答就是語言就被設計成這樣。特別是C語言編寫的程序能夠快速的運行并且使用很少的內存;C語言中指出C的關鍵是“相信程序員”,“許多操作被定義為如何在目標機器的硬件上使用它”。另外,C的模型是透明的,所以C或是C++開發人員可以使用它,通常評估一個結構。當然,現實會有很大的差距;大量優化的編譯器和運行時間要比一般的情況下有更好的性能。
很多的開發者選擇C, C++,或是Objective-C來簡化其他組件的接口。許多工具都有C的接口,大多數語言的基礎設施都可以通過C語言的庫。然而,許多其他的編程語言都有C的接口,兩種方法為C路徑和其他系統通過接口來調用。因此,這并不重要,重要的理由是選擇哪種語言。
當使用C, C++,和Objective-C的開發人員使用這些語言時可以減少使用庫的危險。最新的C標準還增加了一些安全功能,尤其是那些對字符段的處理。C標準仍然錯誤的提供易于使用動態調整大小函數的asprintf()或是類似的函數。但是很多現在的系統使用asprintf()。C語言也可以使用GString類型的glib庫,strlcpy/strlcat提供,或是其他解決問題中的一個。C++程序可以使用std::string或是它的類似內容。類似,Objective-C具有NSString和NSMutableString類。但是這些設施只能在一定程度上降低風險;即使使用了這些設備犯了錯誤。
你可以用什么語言來寫不安全軟件。例如,SQL注入的漏洞是另一個普通弱點,而這可能使用每種語言。然而,大多數語言提供的易于使用,和避免發生問題的機制。我要很小心的使用這個機制…但是對C, Java,和其他語言來說的。
有些語言通常是通過計數器緩沖區溢出,讓你暫時停止保護逃逸機制。這些逃逸機制很容易發現和與精心寫的代碼隔離開來。他們把不安全的隔離成很小的部分,從而降低風險。在很多情況下,你可以重新實現內存緩沖區高速緩存;你可以啟動一些漏洞即使緩沖區存在保護時。但是這種重新實現是明顯的,在很多的語言中,人們必須要努力避免緩沖區溢出問題。與其相反的是,在C, C++, 和 Objective-C里,你必須做一些附加的工作來避免這種問題。
創建安全軟件時,也會遇到一些附加的挑戰。我知道沒有辦法安全的擦除Java里的數據。這是因為Java里沒有.NET的SecureString的功能;由于內存分配和垃圾收集器怎么實現多次拷貝,Java的結構是在結束在內存中。這不是Java的特點,很難安全擦除數據在多種語言中。但是Java中,它可以比較容易通過建立一個小的非Java模塊來擦除一些數值;該程序的其余部分仍然受到保護不會出現緩沖溢出。此外,利用額外的內存拷貝需要訪問大量的程序和運行環境。安全擦除往往是一個有用的減少損傷的措施。相比之下,緩沖區溢出有時候可以直接減少可以利用的連接,有時只要通過網絡連接。一般情況下,緩沖區溢出要比大多數其他語言帶來的問題更危險。
這很難使用C, C++, 和 Objective-C來編寫安全軟件。大多數的語言都可以內嵌和防止緩沖區溢出保護…但是C, C++, 和Objective-C例外。另一方面,他們使用這種原因。
開始運行每一個新的安全相關程序,就要仔細的考慮下程序語言。選擇一個更安全的語言是很有必要的,這樣就可以去除潛在的安全漏洞,其中包括緩沖區溢出的心臟出血漏洞。另外,計算機變得更加強大,在很多情況下可以進行交易一些性能。更重要的是,在開始編寫新的程序時,使用另外一種編程語言就幾乎變成了零成本。我相信用不太安全的語言時,和重寫代碼需要花費很多努力。但是,使用幾乎任何不是C, C++,或是Objective-C,至少會消除緩沖區溢出和緩沖區溢出漏洞會有很大的影響。
我已經確定了更安全的語言作為一個更復雜的方法,因為切換一個不常用的程序,用不同的語言來實現安全是要花費很多的時間的。
3.8 完全靜態分析器
一個完全的靜態分析器可以被稱為聲音靜態分析器,用來發現某個特定的漏洞。創建這些類型的工具就是在調整C語言。然而,程序有時不得不限制他們使用的架構,并且開發人員必須提供附加的注解資料。因為這些工具集中發現一切問題,他們往往會報告不存在漏洞,這就必須要分析決定是否真的存在漏洞。但是,如果它應對所有的漏洞,權衡是很有必要的。
3.9 完全認為的核對
一個完全徹底不獨立的人為軟件檢查,主要集中在確保安全性和發現漏洞,這是發現漏洞的最佳方式。這些評論又被稱為審核,在展示時,這個軟件是很脆弱的。
它的觀念是通過人為審核要比通過工具的啟發式技術來查找漏洞更直觀。更重要是實驗數據證實了這一點。例如,Kupsch和Miller發現使用第一原理弱點評估方式的人為審計分析一個示例程序比使用Coverity Prevent和Fortify Source Code Analyzer更全面。人為審核也會出現意外,但是這樣的評估會做的相當不錯。在Kupsch實驗室,FPVA人為檢查發現了15個嚴重的安全漏洞;Fortify發現了6個,Coverity發現了1個,既不是自動化工具發現也不是由人的審查發現。
但是人工核查的缺點也是顯而易見的:這需要努力和專業知識來做這樣的審核,改變也要審核。人為審查不適合用于所有的軟件,甚至當它在面臨很復雜的情況時。
請注意,這種審核和以前的可以接受的典型的、簡單的回顧是不同的。心臟出血漏洞是試圖避免漏洞開發人員發現的,被另一個審核接受。然而,補丁的審核通常是是功能的改善和尋找安全漏洞的過程,所以很容易讓他們錯過漏洞。正如前面提到的,人為審查每個補丁來要求每個領域的有效認證,要抓住這點…但是現在代碼的存在,只是審核新的補丁是不夠的。試圖在這一點上單獨審查每一份文件的補丁可能是不符合成本效應的。此外,補丁的審核可能錯過重要問題。一個單獨的審核關注整個系統的漏洞是很有效的。
這種審核確實可以發現。事實上,在心臟出血漏洞被發現的同時,TrueCrypt的一個重要組成部分的安全審查發布了。
在很多情況下軟件應該進行修改和簡化,在審查之前。我認為對于OpenSSL是尤其重要的。那些復雜的程序都很難為工具和人為的評估。OpenSSL使用的復雜結構,這就讓它很難被人和機器發現。
3.10格式化方法
但是如果你真的想在某種程度上肯定什么是這個程序要做的?存在著一系列的方法叫做“格式化方法”,這要比上面列出的技術方法更有信心。格式化的方法包括使用“嚴格的數學技術和工具來規范、設計和驗證軟件和硬件系統。”由于使用格式化方法是有困難的,他們更可能是在目前的小程序和模塊上,是絕對可以使用格式化方法的。此外,如果你真的想要擁有很高的信任程序,格式化的方法仍然是實現這一信心的唯一途徑。
這有很多方面可以使用格式化方法。有些人只用格式化的方法來創建規范,不會使用格式化的方法來多做什么。有些人可能會想的多一點,證明有關規范的一些聲明或是改進的規范走向更具體的模型。這些方法都不會發現心臟出血漏洞。對于心臟出血漏洞來說,格式化方法要創造關于代碼的證明,在源代碼和可執行代碼水平上,這就是我最關心的。
在關于有關代碼注釋的系統的實踐證明,值得一提的是,注解系統通常可以在各種不同的方式來使用簡化的證明。為了了解更多的信息,請參見我有關注釋系統的評論。
如果你對更多的感興趣,特別是支持格式化的FLOSS工具,請參閱從我的類的格式化方法來開發安全的軟件。一個有趣的格式化方法工具套件是Toccata,它結合了Frama-C和Why3,以及許多自動化和互助工具。通過組合這些不同的工具來證明程序的正確,在比以前使用更少的努力。更重要的是,他們可以處理C的大量子集;而最正規的方法是不能做到的。SPARK 2014就是基于Ada的,但是可以讓你證明相關程序的聲明,以及他們最近和Toccata聯系在了一起。
正式驗證程序的實例中有seL4,CompCert C,cakeML,Tokeneer和iFACTS。
四、前提條件
很多的技術有重要的先決條件;讓我們來討論下。
4.1 在沒有特別要求時內存分配
許多靜態技術可以對抗心臟出血漏洞的缺陷,包括使用人工核查來對抗,因為OpenSSL的代碼很復雜。代碼只有簡單了才能安全。
許多安全軟件開發者首先使用“軟件質量”工具來檢測特別復雜的結構,然后簡化這些結構,這就是安全軟件的產生過程。理想的靜態分析方法由于代碼復雜和變得困難,實用工具來檢測這些代碼的復雜性,簡化他們,在使用靜態分析方法就變得很有效。我認為他們是對的,但是我沒有發現可以支持他們的數據。因此,這似乎是一個合理的想法,但是我很希望有人最終將創建并發布一些科學研究來支持和反駁這個假設。
在任何情況下,簡化的代碼是超過運行工具軟件的。這是一種心態,應該要有不斷的努力來簡化代碼,不然增加運行能力就會增加軟件的復雜性。代碼的重構要使它變的更簡單和清晰,不是不斷的增加新的功能。我們的目標是代碼是對的,而不是代碼很復雜我們看不出問題。
過于復雜的代碼通常會導致安全漏洞。2006年Debian意外事件通過修改軟件來消除valgrind警告來打破OpenSSL的隨機數生成器。但是,修改軟件的人并不真正的了解它。那個人要求幫助,但是OpenSSL的代碼復雜就很難使人找到改變后的漏洞。Cox通過研究發生并得到了以下的結論:“盡量不寫偷懶的代碼,寫井井有條的代碼。你會不可避免的寫一些偷懶沒有組織的代碼。如果有人問到這這個問題,就把它作為代碼不夠好的標志。重新把它變得更簡單和容易理解。”
LibreSSL的開發者使用了OpenSSL代碼和專心用來簡化代碼。LibreSSL-一個OpenSSL的變版,介紹OpenSSL代碼庫的一些問題。他們正在做許多的明智的事,如去掉代碼支持過時的VAX VMS系統。然而,他們刪除了人們關心的代碼。例如,他們去除美國政府使用的FIPS 140-2的認證,這同時也受到了許多民營企業的支持。在使代碼變得簡單和使代碼在很多的環境中都有效之間存在著沖突,最簡單的代碼不能實現什么。很顯然,許多程序可以變得比現在簡單。
4.2 簡化應用程序接口
雖然這稍微的超出了本文的范圍,一個相關的問題就是應用程序接口一般情況下都比較復雜。
大多數加密庫和數據傳輸庫都是很復雜的,他們通常呈現給開發者一個“困惑矩陣的選項和設置”,因此,大量的應用程序和高層次的庫使用不正確的庫來進行加密,導致了系統的漏洞。大都數的問題在瀏覽器中工作中出現,但是他們在其他的代碼上仍然是一個問題。想要了解更多的信息,請看“最危險的代碼世界:在非瀏覽器軟件中驗證SSL證書。”
盡管這在SSL/TLS的使用中是一個技術而不是一個漏洞,但是他們是無關的。加密程序庫是創建的復雜接口的一個組件,所以,它仍然是一個故障組件,簡化代碼。
一個相關問題是底層庫和建立在API加密庫的系統的很難使用。C忽略了像asprintf和reallocarray等功能。因此,程序員必須要解決這些漏洞,但是他們的解決方法常常會出現bug,這些bug會導致漏洞。
4.3 分配和釋放內存
安全的程序必須正常的分配和釋放內存,沒有特別的分配系統和內存緩存系統。至少,它應該很容易禁用和測試它們來確保禁用了他們的程序。一些技術會減輕心臟出血漏洞的出現,因為OpenSSL的內存分配方式。
基本問題是OpenSSL包括未分配的內存的應用程序特定的緩存空間。目的就是要加快分配在相同數量的重復指令。在默認情況下,OpenSSL正常分配內存,但是在內存區域沒有被使用時是不會解除分配的,在很多情況下,把該區域變成未使用的區域變成空閑的,來使它可以立刻重啟。這個緩存列表顛覆了一些操作系統和C的運行的一些的機制,因為他們并不是總得到通知在內存不使用時。
Theo de Raadt提到:“幾年前我們增加利用措施到libc malloc和mmap,這樣一來更多的bugs就會暴露出來。這種存儲器會導致死機,甚至是核心的崩潰。分析這些bug,之后永久的維護系統。其他的調試工具也會達到這個目的。在很大的程度上說,這基本就沒有性能上的損失。但是在那個時候OpenSSL添加了malloc & free的封裝,使庫存在緩存中。因為一些平臺的性能下降,甚至如果你建立防護技術引入malloc() 和free(),這就無效了。在所有平臺上,由于選項是默認的,并且Ted的測試表明你不能關掉它。因為他們沒有測試它的年齡。所以后來的bug顯示了在該層的內存的泄漏內容。如果內存通過free得到了恰當的返回,這就可能被munmap捕捉到,并激發保護程序的崩潰而不是泄漏你的密碼。”
似乎有很多在什么地方出來問題的困惑和OpenSSL的內存分配方法,Chris Rohlf取得了一些有益的驗證。我覺得這些驗證是很重要的,因為我們必須先用理解這個問題在我們修護它之前。特別是Rohlf指出OpenSSL使用的是標準的malloc() C內存分配方式,當它需要一個全新的內存塊時。問題是一旦一個內存塊被分配,OpenSSL自身還在自身的管理存儲器。Rohlf也指出在很多的環境下是與空閑列表是無關的;一個空閑列表把不同的內存分配在了一起,但是許多典型的內存分配系統也提出了不同的內存分配。然而,Rohlf的典型的內存分配的應用來做同樣的事是絕對正確的,關鍵是OpenSSL的實現阻礙了各種緩解措施。關于OpenSSL的內存分配系統有另一個問題,但是我們首先要介紹下一些基本知識。
一般的方法就是處理一些內存的分配和釋放特例,我們的想法就是緩存和重新對某些對象和緩存器在他們沒有被使用時,這中方法可以顯著的提高性能。這種方法的具體例子包括專門處理共同的內存分配的大小,或是用未使用的高速緩存來重新使用對象和內存器。有一些具體的技術來做這些,包括創建一個對象池和一個slab分配器。Glib庫包括一個稱為記憶切片的機制,提高內存的分配性能。許多圖形用戶界面和程序在使用這些方法時,是沒有安全感的。
事實證明,一些方法可以不用解決一些檢測工具如address sanitizer和使用保護頁系統。特別是使用fuzz測試的問題,如果這些工具不被禁用,則fuzz測試就會變得沒有效果了。的確,fuzz測試可能不能檢測到許多超出范圍的讀操作,在使用這些方法時。
關于OpenSSL的報告指出OpenSSL的使用是自我管理的一個交大的內存區域的方法然后在進一步細化。這是一個使用slab分配器或是儲存器切片時要發生的。使用這個方法就是想要提高性能,在這些情況下使用address sanitizer和保護頁系統來抑制檢測完全的讀溢出。舊的版本稱這要發生什么。但是我已經鉆研了更多的OpenSSL代碼,而這似乎并沒有在OpenSSL中的真實性。這對于心臟出血漏洞來說是個好消息。盡管如此,這些類型的分配方案是比較常見的,而且我知道沒有人說道這些方法的風險。
安全性軟件必須要避免使用內存緩存系統,尤其是那些與一種分配機制聯合在一起形成一個分配請求。如果不是這樣的話,他們至少提供一個簡單的證據機制來禁用它們,并要使用該機制作為其回歸測試套件的一部分。OpenSSL有一個禁用機制,但是不再被使用,并且在任何情況下很少有人能了解這種機制,它可以禁用安全分析工具的很多工程。
我們還需要修改我們的教育材料,是開發人員和測試人員都知道內存緩存系統會嚴重妨礙安全分析。在我的演講中我已經提出了一些材料來開發安全軟件,其他人員也一樣需要。
從長遠來看,這或許應該是使用C的標準接口在freelists緩存/ slab分配器。如果有標準的接口,你們工具可以很容易的修改和自動調解他們。
4.4 使用標準的FLOSS許可證
這是我的推測,我相信如果OpenSSL使用標準的推廣的許可證來進行代碼審核會有更多的貢獻發生。OpenSSL使用的奇怪的變量許可證是GPL和LGPL。因為GPL是一個最常見的FLOSS許可證。在很多情況下這種不相容性是通過圍繞一個許可證漏洞的,或是使用許可證除了在軟件中通過使用OpenSSL。不過這個詭異的證書意味著很多人更喜歡GPL或LGPL會情不自禁的禁止或是審核OpenSSL。一些人喜歡限制較少的許可證,這些也有很少的幫助,這不是一個標準的證書。
我確實有一些證據表明非標準證書是一個問題。一個完全獨立的軟件包GnuTLS在最初是專門創建的。因此使用標準GPL許可證的軟件能夠輕易的使用SSL/TLS。OpenSSL的LibreSSL轉變成了2-clause BSD許可證,當他們寫了新的代碼,相比與OpenSSL許可證。
在很長的一段時間了,廣泛的使用FLOSS證書對FLOSS項目來說是很重要的。1999年Bruce Perens指出:“如果使用這里被列出的一個,就不用寫新的許可證了。”后來Open Source Initiative創建了License Proliferation Project,指出許多許可證“和其他開源代碼的許可證是不兼容的,嚴重的限制了開發人員的方法,開發人員僅僅是擴大開源軟件的創新方式。”一個重要結果是OSI直接列出了開源代碼許可證的頁面,只是Popular Licenses,這是“流行,廣泛使用,或是強大的社區。”
大多數的FLOSS是基于GPL, LGPL, MIT/X, Revised BSD,BSD 2-Clause或是Apache 2.0 licenses。我建議限制FLOSS程序的許可證列表。你可以添加一些;在OSI的流行許可名單包括更多。然而,這里的問題是OpenSSL許可證根本就不是一個公共無可證。更重要的是它是一個廣泛使用的不兼容的許可證的非標準證書。如果可能的話,最好用一般通用的許可證代替。
五、什么會減少心臟出血漏洞的影響?
什么能減少心臟出血漏洞的影響或是完全消除?畢竟當漏洞出現,你想減少影響。下面有一些方法。
5.1 一旦標準內存分配器被替代,啟用內存分配器的防御。
許多系統包括減少損壞的內存分配機制,有的有時可以發現問題。這不能對抗問題,但是能夠減少影響,例如:
一個常見的方法就是內存在分配和釋放時零出,這就意味著如果數據顯示,這不太可能是有趣的事。
在2014.4.29,David Wagner提出了一個有趣的選擇:“使用一個特定的內存分配器,它能夠給每個對象一個隨機的地址。在一個64位系統上,會有一個48位的地址空間,這一切都在離分配對象遠的地方,這個心臟出血漏洞不會透露任何其他對象的信息。這可以被納入標準內存分配器。注明:我不主張這么做,我不是說這是最好的防御,我只是回應你的要求想法來阻止它。”
OpenBSD的malloc支持保護頁,如前所述。特別是它的G和P