網絡技術是從1990年代中期發展起來的新技術,它把互聯網上分散的資源融為有機整體,實現資源的全面共享和有機協作,使人們能夠透明地使用資源的整體能力并按需獲取信息。資源包括高性能計算機、存儲資源、數據資源、信息資源、知識資源、專家資源、大型數據庫、網絡、傳感器等。 當前的互聯網只限于信息共享,網絡則被認為是互聯網發展的第三階段。 檢查代碼中的安全性缺陷,是軟件創建過程中的一個關鍵要素,它與計劃、設計和測試同等重要。作者經過數年的代碼安全性檢查總結出了標識模式和最佳做法,開發人員可以按照該總結來捕獲潛在的安全漏洞。首先,要檢查代碼運行的環境,考慮運行代碼的用戶的角色,以及研究代碼可能存在的任何歷史安全性問題。在理解這些背景問題之后,就可以搜尋特定的安全漏洞了,包括 SQL 注入式攻擊、交叉站點腳本和緩沖區溢出。此外,可以搜尋并修改某些紅色標記(如變量名“password”、“secret”)和其他明顯但常見的安全性錯誤。我的大部分工作就是檢查別人的代碼,尋找安全性錯誤。不可否認,這并不是我的首要任務(我的首要任務是設計檢查和威脅建模),但是,我的確因此而接觸到了大量的代碼。 希望您明白,檢查其他人的代碼是一件好事的同時,卻并不是創建安全軟件的方式。通過設計、編寫、測試以及編寫有關安全系統的文檔,然后安排時間進行安全性檢查、培訓和工具使用,才可以生產出安全的軟件。僅僅依靠設計、編寫、測試以及編寫有關項目的文檔,然后尋找安全性錯誤并不能創建安全的軟件。代碼檢查只是該過程的一部分,其自身不能創建安全的代碼。 在本文中,我不對代碼安全漏洞(例如,整數溢出攻擊、SQL 注入式攻擊以及緩沖區溢出)加以論述;您可以通過閱讀書籍(如我撰寫的 Writing Secure Code,Microsoft Press®,2002)來了解這些問題。但是,我將從一個較高的角度來審視代碼檢查過程中要注意的問題。在正式開始之前,盡管我希望指出這就是我檢查代碼中安全性錯誤的方式,但是您沒有必要以這種方式來檢查代碼,而且我也不能保證這就是檢查某種缺陷的最完美形式。在查看代碼時,我會記錄下自己頭腦中的想法,希望這些想法對您會有所幫助。 我認為有三種方法可用于檢查代碼:深入分析、快速分析以及混合方法。我傾向于將重點放在混合方法上,因為它的優勢在于可快速覆蓋大部分范疇;如果我認為某些地方需要進行更深入的分析,我會做個標記以便稍后對它進行代碼檢查,這可能會牽涉到相關領域其他的專家提示。但是現在,讓我來介紹一下最初的快速代碼檢查,我喜歡稱之為 Sweep“n”Tag 方法(掃描與標記方法),該方法快速掃描代碼并標記要求進一步檢查的代碼。以下是我的這一過程的概要。 分配時間和精力 我使用一個優先級系統來確定檢查代碼需要花費的相關時間。該系統根據潛在的損壞程度建立,這種潛在的損壞程度體現為安全漏洞是否被利用并且是否存在可能的攻擊。配額系統基于下列特征: 默認情況下代碼是否運行? 是否利用提升的權限運行代碼? 是否對網絡接口進行代碼偵聽? 網絡接口是否未經身份驗證? 代碼是否以 C/C++ 編寫? 代碼是否具有安全漏洞的歷史? 安全性研究人員仔細是否審查過該組件? 代碼是否處理敏感或者專有數據? 代碼是否可重復使用(例如,DLL、C++ 類標頭、庫或者程序集)? 根據威脅模型,該組件是處于高風險環境中,還是易于遭受很多高風險威脅? 如果我從該列表得到多于三個或四個肯定的回答,那么我將對代碼進行更進一步的檢查。事實上,如果代碼對傳輸控制協議 (TCP) 或用戶數據報協議 (UDP) 套接字進行偵聽,并且在默認情況下處于運行狀態,那么請準備花費大量時間來檢查代碼。 在尋找安全性錯誤的過程中,我傾向于檢查三種主要代碼類別:C/C++ 代碼、Web 服務器應用程序代碼(例如,ASP、ASP.NET、CGI 和 Perl )以及托管代碼(主要是C#,還有若干 Visual Basic®.NET)。 應該注意,每種語言之間都有一些細微的差異。首先,關于 C 和 C++ 的首要問題是緩沖區溢出。不可否認,還有其他問題存在,但是在同一句話中聽到“緩沖區”和“溢出”這些單詞時,您幾乎就可以確認這是涉及到 C 或 C++ 了。高級語言(例如,C#、Visual Basic .NET 和 Perl)應該沒有緩沖區溢出問題。如果有,錯誤可能存在于運行時環境,而不在檢查的代碼中。然而,這些語言通常會用于 Web 服務器應用程序代碼中,而且會面臨其他類型的缺陷。緩沖區溢出最令人討厭,因為攻擊者可以將代碼注入到運行進程中并對它進行襲擊。因此,讓我們首先看看緩沖區溢出。 C 和 C++ 中的緩沖區溢出 緩沖區溢出是軟件行業中的棘手問題,您應該盡最大的努力將它們從您的代碼中根除。然而,最好是首先確保它們沒有進入到代碼中。 我有兩種檢查緩沖區溢出的方法。第一種,標識所有應用程序的入口點,尤其是網絡入口點,同時跟蹤在代碼中移動的數據并審查數據的處理方式。我假定所有的數據格式都不正確。當查看涉及數據(讀取數據或寫入數據)的任何代碼時,我會問“是否存在可導致該代碼失敗的數據版本呢?”這種方法檢查得很徹底,但是很費時。另一種方法是尋找已知的和潛在危險的構造并跟蹤數據回到入口點。例如,請看下面的代碼: void fuction(char *p) { char buff[16]; ••• strcpy(buff,p); ••• } 如果我看到這樣的代碼,我將跟蹤變量 p 返回到它的源,如果它來自于我不信任的位置,或者它所復制的來源的有效性未經檢查,那么我就知道我發現了一個安全性錯誤。請注意,strcpy 本身并不危險或不安全。而危險的是使函數變得如此可怕的數據。如果檢查數據格式正確,則 strcpy 可以是安全的。當然,如果您判斷出錯,就會有安全性錯誤了。我也檢查了“n”字符串操縱函數(例如 strncpy),因為您還需要檢查緩沖區大小計算是否正確。 我對處理標記文件格式的代碼比較提防。我指的標記文件由幾個塊區組成,每個塊區都具有說明下一塊區數據的標頭。MIDI 音樂格式就是一個很好的例子。在名為 quartz.dll(處理 MIDI 文件)的 Windows 組件中發現并修改了一個嚴重的安全性錯誤。格式錯誤的 MIDI 構造將導致處理文件的代碼失敗,甚至更糟糕。您可以在 Unchecked Buffer in DirectX Could Enable System Compromise 中閱讀有關該錯誤的詳細信息。 我要注意的另一個構造如下所示: while (*s != '\\') *d++ = *s++; 這個循環受到源中字符的限制,但不受目標大小的限制。基本上,我使用下面的正則表達式來掃描 *x++ = *y++: \*\w+\+\+\s?=\s?\*\w+\+\+ 當然,也可以使用 *++x = *++y,因此您也需要對它進行掃描。我想再一次強調,該構造沒有危險(除非源不受信任),因此您需要確定源數據的可信性。 接下來應該注意的是另一個與緩沖區溢出相關的問題:整數溢出漏洞。 C 和 C++ 中的整數溢出 如果您不知道整數溢出攻擊是什么以及如何修復它們,那么您應該首先閱讀我的文章“Development Impacts of Security Changes in Windows Server 2003”。當執行算法以計算緩沖區大小并且計算導致上溢或下溢時,真正的安全性漏洞會隨同這些缺陷一起出現。請看下面的示例: void func(char *b1, size_t c1, char *b2, size_t c2) { const size_t MAX = 48; if (c1 + c2 > MAX) return; char *pBuff = new char[MAX]; memcpy(pBuff,b1,c1); memcpy(pBuff+c1,b2,c2); } 上面的代碼看起來沒有問題,但如果將 c1 和 c2 相加,結果超過 232-1,您就會意識到有問題了。例如,0xFFFFFFF0 和 0x40 相加的結果為 0x30(十進制為 48)。當這些值用于 c1 和 c2 時,加起來的和可以通過大小檢查,然后代碼會將大約 4GB 復制到 48 個字節的緩沖區。這樣就會出現緩沖區溢出!類似于這樣的許多錯誤都可以被利用,使攻擊者將代碼注入您的進程中。 當檢查 C 和 C++ 代碼的整數溢出問題時,我將查找運算符 new 和動態內存分配函數(alloca、malloc、calloc、HeapAlloc 等等)的所有實例,然后確定如何計算緩沖區大小。然后,我會詢問自己下列問題: 這些值可以超過特定的最大值嗎? 這些值可以小于零嗎? 數據是否被截斷(將 32 位值復制到 16 位值中,然后復制 32 位大小)? [page_break] 我的一位 Microsoft 同事的經驗是:如果在用于比較的表達式中執行數學運算,那么就有可能上溢或下溢數據。如果將計算用于確定緩沖區大小,情況會更加糟糕,尤其是,如果一個或多個緩沖區大小計算元素已經被攻擊者破壞。 任何語言中的數據庫訪問代碼 作為一般規則,開發人員以高級語言(如 C#、scripting 語言以及類似的語言)編寫數據庫應用程序。相對而言,很少以 C 和 C++ 編寫數據庫代碼,但是有些人使用各種 C/C++ 類庫,例如 MFC 中的 CDatabase 類。 其中可以檢測到兩個問題。第一個是連接字符串,包括硬編碼的密碼或者使用管理員帳戶的連接。可以檢測到的第二個問題是 SQL 注入式攻擊漏洞。 當我查看托管代碼時,首要的事情就是搜索 System.Data 命名空間的所有代碼,尤其是System.Data.SqlClient。當我看到這些時,就要格外謹慎了!接下來,我在代碼中查找如“connect”這樣的單詞(它通常出現在連接字符串旁)。該連接字符串有兩個需要關注的屬性:連接 id(通常寫為 uid)和密碼(通常寫為 pwd)。這些都是潛在的安全漏洞: DRIVER={SQL Server};SERVER=hrserver;UID=sa;PWD=$esame 事實上,在上面的示例中有兩個錯誤。第一,以系統管理員帳戶 sa 進行連接;這違背了授予最低權限這一必要原則。代碼永遠不應該以系統管理員帳戶連接到數據庫,因為當該帳戶被惡意用戶使用時,它會給數據庫帶來災難性后果。第二,密碼是硬編碼的。有兩個理由可以說明這是錯誤的:第一個理由,密碼會被發現;其次,如果更改密碼,又該如何處理?(您將必須更新所有客戶端。) 接下來的主題是 SQL 注入式攻擊。SQL 注入的癥結在于使用字符串連接來構建 SQL 語句。當掃描代碼時,我將查看 SQL 語句的創建位置。一般而言,這涉及搜索諸如“update”、“select”、“insert”、“exec”以及任意我知道使用的表名或數據庫名之類的單詞。為了幫助解決問題,我使用下面的 ildasm.exe 來審查托管程序集: ildasm /adv /metadata /out:file test.exe 然后,在生成的輸出中分析“User Strings”部分。如果發現任何使用字符串連接的數據庫查詢,那么這就是一個潛在的安全缺陷,必須使用參數化查詢來對其進行修復。 使用字符串連接構建存儲過程也不能防止 SQL 注入。簡而言之,字符串連接加上 SQL 語句會使情況變糟,而字符串連接加上 SQL 語句再加上系統管理員帳戶就無異于一場災難。 任意語言中的 Web 頁代碼 基于 Web 頁的應用程序中最常見的錯誤是跨站點腳本 (XSS) 問題。盡管我也會查找其他問題(例如 SQL 注入和拙劣的加密),但是 XSS 錯誤相當普遍。核心 XSS 漏洞可能會在受害者的瀏覽器中顯示不受信任的用戶輸入,所以我首先搜索所有將數據發送給用戶的代碼構造。例如,在 ASP 中查找 Response.Write 和 <%= %> 標記。接下來,分析被寫入的數據從而查看它的來源。如果該數據來自 HTTP 實體(例如,窗體或查詢字符串),并且沒有檢查有效性就將它發送到用戶的瀏覽器,那么就會存在 XSS 錯誤。下面是一個非常簡單,但又最為常見的 XSS 示例: Hello, <% Response.Write(Request.QueryString("Name")) %> 正如您看到的那樣,“Name”參數被發送回用戶,而沒有首先檢查它是否有效及格式規范。 任意一種語言中的機密與加密 一些開發人員喜歡在代碼中存儲機密數據(例如,密碼和加密密鑰),并創建自己的不可思議的密碼算法。請不要這么做! 我首先尋找變量名和名稱中包含“key”、“password”、“pwd”、“secret”、“cipher”以及“crypt”的函數。任何內容都需要加以分析。您可能經常獲得貌似正確但實際錯誤的“密鑰”,但是要注意其他幾項,它們也許會產生嵌入式的機密數據或“不可思議的”加碼系統。搜索密碼算法的同時,我也尋找 XOR 運算符,因為它們經常用于加密。最糟糕的代碼是使用嵌入式密鑰來 XOR 數據流的代碼! Visual Basic 和 C++ 中的 ActiveX 控件 當我檢查新的 ActiveX® 控件時,我始終想問一個問題:為什么不使用托管代碼來編寫?我問這個問題的原因在于,托管代碼允許部分信任方案,而 ActiveX 卻不是。 接下來,我分析控件的所有方法和屬性(.IDL 文件是進行該操作最好的切入點),并且將自己設想為一個進行惡意攻擊的人。我能利用這些方法或者屬性來做一些什么樣的危險事情呢?通常,很多方法以“VerbNoun”格式(動詞 + 名詞)進行命名,例如 ReadRegistry、WriteFile、GetUserName 和NukeKey,所以我尋找發音復雜的動詞和屬于敏感資源的名詞(資源)。 例如,如果攻擊者可以訪問用戶硬盤上的任何文件,并且可以將它發送到任意位置(例如,攻擊者控制下的 Web 站點),那么 SendFile 方法就存在潛在的危險!任何訪問用戶計算機上的資源的操作都需要進一步的審查。 如果該控件被標記為可安全編寫腳本 (SFS),我會進行額外的檢查工作,因為該控件可能會在 Web 瀏覽器中在不警告用戶的情況下被調用。如果該控件在安裝時執行 ATL IobjectSafetyImpl 接口或設置下面的“可安全編寫腳本”或“可安全激活”實現類別,則您可以確定它是否為 SFS: [HKEY_CLASSES_ROOT\CLSID\<GUID>\Implemented Categories\{7DD95801-9882-11CF-9FA9-00AA006C42C4}][HKEY_CLASSES_ROOT\CLSID\<GUID>\Implemented Categories\{7DD95802-9882-11CF-9FA9-00AA006C42C4}] 我之前曾提到,通過 SendFile 方法訪問和發送用戶文件不是好的做法。實際上,如果我能夠訪問 SendFile 方法,并可以基于該方法返回的錯誤代碼來確定用戶硬盤驅動器中是否存在文件,那么它就是一個隱私錯誤。 小結 這是我檢查代碼時通過的第一個非常高級別的審查。這些錯誤中的大多數都非常簡單,有人可能會爭辯說開發人員不應該犯這樣的錯誤,但他們確實會犯這樣的錯誤。然而,了解到有人會對您代碼的進行安全性檢查這一點,通常會使您將編寫更為安全的代碼放在第一位。 您可能還注意到,在包含某些缺陷類型的常見問題中,多數是由不受信任的輸入造成的。當在檢查代碼時,您應該始終詢問數據從何而來、是否值得信任。 網絡的神奇作用吸引著越來越多的用戶加入其中,正因如此,網絡的承受能力也面臨著越來越嚴峻的考驗―從硬件上、軟件上、所用標準上......,各項技術都需要適時應勢,對應發展,這正是網絡迅速走向進步的催化劑。 |
溫馨提示:喜歡本站的話,請收藏一下本站!