C本質上是不安全的編程語言。例如如果不謹慎使用的話,其大多數標准的字符串庫函數有可能被用來進行緩沖區攻擊或者格式字符串攻擊。但是,由於其靈活性、快速和相對容易掌握,它是一個廣泛使用的編程語言。下面是針對開發安全的C語言程序的一些規范。
1.1.1 緩沖區溢出
避免使用不執行邊界檢查的字符串函數,因為它們可能被用來進行緩沖區溢出攻擊。下面是應該避免使用的函數。同時,也列出了每個函數相應的比較安全的替換方式。
不使用strcpy(),使用strncpy();
不使用strcat(),使用strncat();
不使用sprintf(),使用snprintf();
不使用gets(),使用fgets()。
在上面的前三個中函數中,每個替代函數的“n”表示了使用的緩沖區的大小。最后一個函數的“f”,表示格式,它允許用戶指定期望的輸入的格式。這些替換方程強制程序員定義使用的緩沖區的尺寸以及確定輸入的類型。
1.1.2 格式化字符串攻擊
該類攻擊往往與緩沖區溢出相關,因為它們主要利用了某些函數的假設,例如sprintf()和vsprintf()假設緩沖區的長度是無限的。然而即使使用snprintf()替換sprintf()也無法完全保護程序不受格式化字符串的攻擊。這些攻擊通過直接將格式說明符(formatspecifiers)(%d,%s,%n等)傳遞到輸出函數接收緩沖區來進行。
例如,以下的代碼就是不安全的snprintf(buffer,sizeof(buffer),string)這種情況下,可以在字符串中插入格式說明符來操縱內存的棧,來寫入攻擊者的數據(這些數據中包含小的程序代碼,並可由處理器接着執行)。對以上的例子建議使用下面的代碼。
snprintf(buffer,sizeof(buffer),“%s”,string)進行格式字符串攻擊不太容易。首先攻擊者必須能獲得內存棧的內容情況(或者從應用導出或者使用調試器),然后必須知道如何精確訪問特定的內存空間來操縱棧中的變量。
執行外部程序推薦使用exec()函數而不是system()函數來執行外部程序。這是因為system()接收整個命令行的隨機的緩沖區來執行程序。
snprintf(buffer,sizeof(buffer),"emacs%s",filename);
system(buffer);
在以上的例子中,可以通過使用分號利用文件名變量在sehll中插入額外的命令(例如文件名可以是/etc/hosts;rm*,這將在顯示/etc/hosts目錄文件的同時,刪除目錄中的所有文件)。而exec()函數只保證第一個參數被執行:
execl("usr/bin/emacs","usr/bin/emacs",filename,NULL);
上面的例子保證文件名僅僅作為一個參數輸入Emacs工具,同樣它在Emacs命令中使用完全的路徑而不是使用可以被攻擊者利用的PATH環境變量。
1.1.3 競爭條件
進程需要訪問資源時(無論是磁盤、內存或是文件)通常需要執行兩個步驟:
1、首先測試資源是否空閑可用;
2、如果可用,就訪問該資源,否則它等到資源不再使用為止再去訪問它。當另一個進程在步驟1和2之間想要訪問同一個資源時就出現問題了。
這會導致不可預測的結果。進程可能會被鎖定,或者一個進程劫持獲得了另一個進程的較大的權限而導致安全問題。攻擊主要集中在有較大權限的程序上(稱為setuid程序)。競爭條件攻擊通常利用程序執行時可以訪問到的資源。另外權限低的程序也存在安全風險,因為攻擊者可能會等待有較高權限的用戶執行那個程序(例如root),然后進行攻擊。
下面的建議有助於緩解競爭條件(racecondition)攻擊:
在進行文件操作時,利用那些使用文件描述符的函數而不能使用那些使用文件路徑的函數(例如使用fdopen()而不能使用fopen())。文件描述符使得惡意的用戶在文件打開時或是在原始的進程對文件進行操作前,無法使用文件連接(符號式的或是物理的)來改變文件。
在寫文件甚至在讀文件時使用fcntl()和flock()函數來對文件加鎖,這樣它們就不能被其他進程訪問。它幾乎可以建立原子級的操作。
謹慎操縱臨時文件,因為它往往會導致競爭條件。
1.1.4 檢驗有效的返回值
檢驗有效的返回值非常重要。一個例子是舊的/bin/login的實現中不檢驗錯誤的返回值,導致當它找不到/etc/passwd文件時返回root的訪問權限。如果該文件損壞了,那么這種情況是合理的,但如果該文件存在只是無法訪問,那么這就是一個大問題。