1. 程式人生 > >【安全開發】C/C++安全編碼規範

【安全開發】C/C++安全編碼規範

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的訪問許可權。如果該檔案損壞了,那麼這種情況是合理的,但如果該檔案存在只是無法訪問,那麼這就是一個大問題。