在 上一篇專欄文章中,描述了高水平的緩沖區(qū)溢出攻擊,以及討論了為什么緩沖區(qū)溢出是如此嚴(yán)重的安全性問(wèn)題。本專欄文章的主題是,通過(guò)防御性編程保護(hù)代碼不受緩沖區(qū)溢出攻擊。我們將論及 C 編程語(yǔ)言中的主要安全性陷阱,顯示應(yīng)該避免特殊構(gòu)造的原因,以及演示推薦的編程實(shí)踐。最后,將討論有助于有效防止緩沖區(qū)溢出的其它技術(shù)。 C 中大多數(shù)緩沖區(qū)溢出問(wèn)題可以直接追溯到標(biāo)準(zhǔn) C 庫(kù)。最有害的罪魁禍?zhǔn)资遣贿M(jìn)行自變量檢查的、有問(wèn)題的字符串操作(strcpy、strcat、sprintf 和 gets)。一般來(lái)講,象“避免使用 strcpy()”和“永遠(yuǎn)不使用 gets()”這樣嚴(yán)格的規(guī)則接近于這個(gè)要求。 今天,編寫的程序仍然利用這些調(diào)用,因?yàn)閺膩?lái)沒(méi)有人教開發(fā)人員避免使用它們。某些人從各處獲得某個(gè)提示,但即使是優(yōu)秀的開發(fā)人員也會(huì)被這弄糟。他們也許在危險(xiǎn)函數(shù)的自變量上使用自己總結(jié)編寫的檢查,或者錯(cuò)誤地推論出使用潛在危險(xiǎn)的函數(shù)在某些特殊情況下是“安全”的。 第一位公共敵人是 gets()。永遠(yuǎn)不要使用 gets()。該函數(shù)從標(biāo)準(zhǔn)輸入讀入用戶輸入的一行文本,它在遇到 EOF 字符或換行字符之前,不會(huì)停止讀入文本。也就是:gets() 根本不執(zhí)行邊界檢查。因此,使用 gets() 總是有可能使任何緩沖區(qū)溢出。作為一個(gè)替代方法,可以使用方法 fgets()。它可以做與 gets() 所做的同樣的事情,但它接受用來(lái)限制讀入字符數(shù)目的大小參數(shù),因此,提供了一種防止緩沖區(qū)溢出的方法。例如,不要使用以下代碼: void main()
{
char buf[1024];
gets(buf);
}
|
而使用以下代碼:
#define BUFSIZE 1024
void main()
{
char buf[BUFSIZE];
fgets(buf, BUFSIZE, stdin);
}
|
C 編程中的主要陷阱
C 語(yǔ)言中一些標(biāo)準(zhǔn)函數(shù)很有可能使您陷入困境。但不是所有函數(shù)使用都不好。通常,利用這些函數(shù)之一需要任意輸入傳遞給該函數(shù)。這個(gè)列表包括:
- strcpy()
- strcat()
- sprintf()
- scanf()
- sscanf()
- fscanf()
- vfscanf()
- vsprintf
- vscanf()
- vsscanf()
- streadd()
- strecpy()
- strtrns()
壞消息是我們推薦,如果有任何可能,避免使用這些函數(shù)。好消息是,在大多數(shù)情況下,都有合理的替代方法。我們將仔細(xì)檢查它們中的每一個(gè),所以可以看到什么構(gòu)成了它們的誤用,以及如何避免它。
strcpy()函數(shù)將源字符串復(fù)制到緩沖區(qū)。沒(méi)有指定要復(fù)制字符的具體數(shù)目。復(fù)制字符的數(shù)目直接取決于源字符串中的數(shù)目。如果源字符串碰巧來(lái)自用戶輸入,且沒(méi)有專門限制其大小,則有可能會(huì)陷入大的麻煩中!
如果知道目的地緩沖區(qū)的大小,則可以添加明確的檢查:
if(strlen(src) >= dst_size) {
/* Do something appropriate, such as throw an error. */
}
else {
strcpy(dst, src);
|
完成同樣目的的更容易方式是使用 strncpy() 庫(kù)例程:
strncpy(dst, src, dst_size-1);
dst[dst_size-1] = '\0'; /* Always do this to be safe! */
|
如果 src 比 dst 大,則該函數(shù)不會(huì)拋出一個(gè)錯(cuò)誤;當(dāng)達(dá)到最大尺寸時(shí),它只是停止復(fù)制字符。注意上面調(diào)用 strncpy() 中的 -1。如果 src 比 dst 長(zhǎng),則那給我們留有空間,將一個(gè)空字符放在 dst 數(shù)組的末尾。
當(dāng)然,可能使用 strcpy() 不會(huì)帶來(lái)任何潛在的安全性問(wèn)題,正如在以下示例中所見:
即使這個(gè)操作造成 buf 的溢出,但它只是對(duì)幾個(gè)字符這樣而已。由于我們靜態(tài)地知道那些字符是什么,并且很明顯,由于沒(méi)有危害,所以這里無(wú)須擔(dān)心 ― 當(dāng)然,除非可以用其它方式覆蓋字符串“Hello”所在的靜態(tài)存儲(chǔ)器。
確保 strcpy() 不會(huì)溢出的另一種方式是,在需要它時(shí)就分配空間,確保通過(guò)在源字符串上調(diào)用 strlen() 來(lái)分配足夠的空間。例如:
dst = (char *)malloc(strlen(src));
strcpy(dst, src);
|
strcat()函數(shù)非常類似于 strcpy(),除了它可以將一個(gè)字符串合并到緩沖區(qū)末尾。它也有一個(gè)類似的、更安全的替代方法 strncat()。如果可能,使用 strncat() 而不要使用 strcat()。
函數(shù) sprintf()和 vsprintf()是用來(lái)格式化文本和將其存入緩沖區(qū)的通用函數(shù)。它們可以用直接的方式模仿 strcpy() 行為。換句話說(shuō),使用 sprintf() 和 vsprintf() 與使用 strcpy() 一樣,都很容易對(duì)程序造成緩沖區(qū)溢出。例如,考慮以下代碼:
void main(int argc, char **argv)
{
char usage[1024];
sprintf(usage, "USAGE: %s -f flag [arg1]\n", argv[0]);
}
|
我們經(jīng)常會(huì)看到類似上面的代碼。它看起來(lái)沒(méi)有什么危害。它創(chuàng)建一個(gè)知道如何調(diào)用該程序字符串。那樣,可以更改二進(jìn)制的名稱,該程序的輸出將自動(dòng)反映這個(gè)更改。 雖然如此, 該代碼有嚴(yán)重的問(wèn)題。文件系統(tǒng)傾向于將任何文件的名稱限制于特定數(shù)目的字符。那么,您應(yīng)該認(rèn)為如果您的緩沖區(qū)足夠大,可以處理可能的最長(zhǎng)名稱,您的程序會(huì)安全,對(duì)嗎?只要將 1024 改為對(duì)我們的操作系統(tǒng)適合的任何數(shù)目,就好了嗎?但是,不是這樣的。通過(guò)編寫我們自己的小程序來(lái)推翻上面所說(shuō)的,可能容易地推翻這個(gè)限制:
void main()
{
execl("/path/to/above/program",
<<insert really long string here>>,
NULL);
}
|
函數(shù) execl() 啟動(dòng)第一個(gè)參數(shù)中命名的程序。第二個(gè)參數(shù)作為 argv[0] 傳遞給被調(diào)用的程序。我們可以使那個(gè)字符串要多長(zhǎng)有多長(zhǎng)!
那么如何解決 {v}sprintf() 帶來(lái)得問(wèn)題呢?遺憾的是,沒(méi)有完全可移植的方法。某些體系結(jié)構(gòu)提供了 snprintf() 方法,即允許程序員指定將多少字符從每個(gè)源復(fù)制到緩沖區(qū)中。例如,如果我們的系統(tǒng)上有 snprintf,則可以修正一個(gè)示例成為:
void main(int argc, char **argv)
{
char usage[1024];
char format_string = "USAGE: %s -f flag [arg1]\n";
snprintf(usage, format_string, argv[0],
1024-strlen(format_string) + 1);
}
|
注意,在第四個(gè)變量之前,snprintf() 與 sprintf() 是一樣的。第四個(gè)變量指定了從第三個(gè)變量中應(yīng)被復(fù)制到緩沖區(qū)的字符最大數(shù)目。注意,1024 是錯(cuò)誤的數(shù)目!我們必須確保要復(fù)制到緩沖區(qū)使用的字符串總長(zhǎng)不超過(guò)緩沖區(qū)的大小。所以,必須考慮一個(gè)空字符,加上所有格式字符串中的這些字符,再減去格式說(shuō)明符 %s。該數(shù)字結(jié)果為 1000, 但上面的代碼是更具有可維護(hù)性,因?yàn)槿绻袷阶址既话l(fā)生變化,它不會(huì)出錯(cuò)。
{v}sprintf() 的許多(但不是全部)版本帶有使用這兩個(gè)函數(shù)的更安全的方法。可以指定格式字符串本身每個(gè)自變量的精度。例如,另一種修正上面有問(wèn)題的 sprintf() 的方法是:
void main(int argc, char **argv)
{
char usage[1024];
sprintf(usage, "USAGE: %.1000s -f flag [arg1]\n", argv[0]);
}
|
注意,百分號(hào)后與 s 前的 .1000。該語(yǔ)法表明,從相關(guān)變量(本例中是 argv[0])復(fù)制的字符不超過(guò) 1000 個(gè)。
如果任一解決方案在您的程序必須運(yùn)行的系統(tǒng)上行不通,則最佳的解決方案是將 snprintf() 的工作版本與您的代碼放置在一個(gè)包中。可以找到以 sh 歸檔格式的、自由使用的版本;請(qǐng)參閱 參考資料。
繼續(xù), scanf系列的函數(shù)也設(shè)計(jì)得很差。在這種情況下,目的地緩沖區(qū)會(huì)發(fā)生溢出。考慮以下代碼:
void main(int argc, char **argv)
{
char buf[256];
sscanf(argv[0], "%s", &buf);
}
|
如果輸入的字大于 buf 的大小,則有溢出的情況。幸運(yùn)的是,有一種簡(jiǎn)便的方法可以解決這個(gè)問(wèn)題。考慮以下代碼,它沒(méi)有安全性方面的薄弱環(huán)節(jié):
void main(int argc, char **argv)
{
char buf[256];
sscanf(argv[0], "%255s", &buf);
}
|
百分號(hào)和 s 之間的 255 指定了實(shí)際存儲(chǔ)在變量 buf 中來(lái)自 argv[0] 的字符不會(huì)超過(guò) 255 個(gè)。其余匹配的字符將不會(huì)被復(fù)制。
接下來(lái),我們討論 streadd()和 strecpy()。由于,不是每臺(tái)機(jī)器開始就有這些調(diào)用,那些有這些函數(shù)的程序員,在使用它們時(shí),應(yīng)該小心。這些函數(shù)可以將那些含有不可讀字符的字符串轉(zhuǎn)換成可打印的表示。例如,考慮以下程序:
#include <libgen.h>
void main(int argc, char **argv)
{
char buf[20];
streadd(buf, "\t\n", "");
printf(%s\n", buf);
}
|
該程序打印:
而不是打印所有空白。如果程序員沒(méi)有預(yù)料到需要多大的輸出緩沖區(qū)來(lái)處理輸入緩沖區(qū)(不發(fā)生緩沖區(qū)溢出),則 streadd() 和 strecpy() 函數(shù)可能有問(wèn)題。如果輸入緩沖區(qū)包含單一字符 ― 假設(shè)是 ASCII 001(control-A)― 則它將打印成四個(gè)字符“\001”。這是字符串增長(zhǎng)的最壞情況。如果沒(méi)有分配足夠的空間,以至于輸出緩沖區(qū)的大小總是輸入緩沖區(qū)大小的四倍,則可能發(fā)生緩沖區(qū)溢出。
另一個(gè)較少使用的函數(shù)是 strtrns(),因?yàn)樵S多機(jī)器上沒(méi)有該函數(shù)。函數(shù) strtrns() 取三個(gè)字符串和結(jié)果字符串應(yīng)該放在其內(nèi)的一個(gè)緩沖區(qū),作為其自變量。第一個(gè)字符串必須復(fù)制到該緩沖區(qū)。一個(gè)字符被從第一個(gè)字符串中復(fù)制到緩沖區(qū),除非那個(gè)字符出現(xiàn)在第二個(gè)字符串中。如果出現(xiàn)的話,那么會(huì)替換掉第三個(gè)字符串中同一索引中的字符。這聽上去有點(diǎn)令人迷惑。讓我們看一下,將所有小寫字符轉(zhuǎn)換成大寫字符的示例:
#include <libgen.h>
void main(int argc, char **argv)
{
char lower[] = "abcdefghijklmnopqrstuvwxyz";
char upper[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char *buf;
if(argc < 2) {
printf("USAGE: %s arg\n", argv[0]);
exit(0);
} buf = (char *)malloc(strlen(argv[1]));
strtrns(argv[1], lower, upper, buf);
printf("%s\n", buf);
}
|
以上代碼實(shí)際上不包含緩沖區(qū)溢出。但如果我們使用了固定大小的靜態(tài)緩沖區(qū),而不是用 malloc() 分配足夠空間來(lái)復(fù)制 argv[1],則可能會(huì)引起緩沖區(qū)溢出情況。
避免內(nèi)部緩沖區(qū)溢出
realpath() 函數(shù)接受可能包含相對(duì)路徑的字符串,并將它轉(zhuǎn)換成指同一文件的字符串,但是通過(guò)絕對(duì)路徑。在做這件事時(shí),它展開了所有符號(hào)鏈接。
該函數(shù)取兩個(gè)自變量,第一個(gè)作為要規(guī)范化的字符串,第二個(gè)作為將存儲(chǔ)結(jié)果的緩沖區(qū)。當(dāng)然,需要確保結(jié)果緩沖區(qū)足夠大,以處理任何大小的路徑。分配的 MAXPATHLEN 緩沖區(qū)應(yīng)該足夠大。然而,使用 realpath() 有另一個(gè)問(wèn)題。如果傳遞給它的、要規(guī)范化的路徑大小大于 MAXPATHLEN,則 realpath() 實(shí)現(xiàn)內(nèi)部的靜態(tài)緩沖區(qū)會(huì)溢出!雖然實(shí)際上沒(méi)有訪問(wèn)溢出的緩沖區(qū),但無(wú)論如何它會(huì)傷害您的。結(jié)果是,應(yīng)該明確不使用 realpath(),除非確保檢查您試圖規(guī)范化的路徑長(zhǎng)度不超過(guò) MAXPATHLEN。
其它廣泛可用的調(diào)用也有類似的問(wèn)題。經(jīng)常使用的 syslog() 調(diào)用也有類似的問(wèn)題,直到不久前,才注意到這個(gè)問(wèn)題并修正了它。大多數(shù)機(jī)器上已經(jīng)糾正了這個(gè)問(wèn)題,但您不應(yīng)該依賴正確的行為。最好總是假定代碼正運(yùn)行在可能最不友好的環(huán)境中,只是萬(wàn)一在哪天它真的這樣。getopt() 系列調(diào)用的各種實(shí)現(xiàn),以及 getpass() 函數(shù),都可能產(chǎn)生內(nèi)部靜態(tài)緩沖區(qū)溢出問(wèn)題。如果您不得不使用這些函數(shù),最佳解決方案是設(shè)置傳遞給這些函數(shù)的輸入長(zhǎng)度的閾值。
自己模擬 gets() 的安全性問(wèn)題以及所有問(wèn)題是非常容易的。 例如,下面這段代碼:
char buf[1024];
int i = 0;
char ch;
while((ch = getchar()) != '\n')
{
if(ch == -1) break;
buf[i++] = ch;
}
|
哎呀!可以用來(lái)讀入字符的任何函數(shù)都存在這個(gè)問(wèn)題,包括 getchar()、fgetc()、getc() 和 read()。
緩沖區(qū)溢出問(wèn)題的準(zhǔn)則是:總是確保做邊界檢查。
C 和 C++ 不能夠自動(dòng)地做邊界檢查,這實(shí)在不好,但確實(shí)有很好的原因,來(lái)解釋不這樣做的理由。邊界檢查的代價(jià)是效率。一般來(lái)講,C 在大多數(shù)情況下注重效率。然而,獲得效率的代價(jià)是,C 程序員必須十分警覺,并且有極強(qiáng)的安全意識(shí),才能防止他們的程序出現(xiàn)問(wèn)題,而且即使這些,使代碼不出問(wèn)題也不容易。
在現(xiàn)在,變量檢查不會(huì)嚴(yán)重影響程序的效率。大多數(shù)應(yīng)用程序不會(huì)注意到這點(diǎn)差異。所以,應(yīng)該總是進(jìn)行邊界檢查。在將數(shù)據(jù)復(fù)制到您自己的緩沖區(qū)之前,檢查數(shù)據(jù)長(zhǎng)度。同樣,檢查以確保不要將過(guò)大的數(shù)據(jù)傳遞給另一個(gè)庫(kù),因?yàn)槟膊荒芟嘈牌渌说拇a!(回憶一下前面所討論的內(nèi)部緩沖區(qū)溢出。)
其它危險(xiǎn)是什么?
遺憾的是,即使是系統(tǒng)調(diào)用的“安全”版本 ― 譬如,相對(duì)于 strcpy() 的 strncpy() ― 也不完全安全。也有可能把事情搞糟。即使“安全”的調(diào)用有時(shí)會(huì)留下未終止的字符串,或者會(huì)發(fā)生微妙的相差一位錯(cuò)誤。當(dāng)然,如果您偶然使用比源緩沖區(qū)小的結(jié)果緩沖區(qū),則您可能發(fā)現(xiàn)自己處于非常困難的境地。
與我們目前所討論的相比,往往很難犯這些錯(cuò)誤,但您應(yīng)該仍然意識(shí)到它們。當(dāng)使用這類調(diào)用時(shí),要仔細(xì)考慮。如果不仔細(xì)留意緩沖區(qū)大小,包括 bcopy()、fgets()、memcpy()、snprintf()、strccpy()、strcadd()、strncpy() 和 vsnprintf(),許多函數(shù)會(huì)行為失常。
另一個(gè)要避免的系統(tǒng)調(diào)用是 getenv()。使用 getenv() 的最大問(wèn)題是您從來(lái)不能假定特殊環(huán)境變量是任何特定長(zhǎng)度的。我們將在后續(xù)的專欄文章中討論環(huán)境變量帶來(lái)的種種問(wèn)題。
到目前為止,我們已經(jīng)給出了一大堆常見 C 函數(shù),這些函數(shù)容易引起緩沖區(qū)溢出問(wèn)題。當(dāng)然,還有許多函數(shù)有相同的問(wèn)題。特別是,注意第三方 COTS 軟件。不要設(shè)想關(guān)于其他人軟件行為的任何事情。還要意識(shí)到我們沒(méi)有仔細(xì)檢查每個(gè)平臺(tái)上的每個(gè)常見庫(kù)(我們不想做那一工作),并且還可能存在其它有問(wèn)題的調(diào)用。
即使我們檢查了每個(gè)常見庫(kù)的各個(gè)地方,如果我們?cè)噲D聲稱已經(jīng)列出了將在任何時(shí)候遇到的所有問(wèn)題,則您應(yīng)該持非常非常懷疑的態(tài)度。我們只是想給您起一個(gè)頭。其余全靠您了。
靜態(tài)和動(dòng)態(tài)測(cè)試工具
我們將在以后的專欄文章中更加詳細(xì)地介紹一些脆弱性檢測(cè)的工具,但現(xiàn)在值得一提的是兩種已被證明能有效幫助找到和去除緩沖區(qū)溢出問(wèn)題的掃描工具。 這兩個(gè)主要類別的分析工具是靜態(tài)工具(考慮代碼但永不運(yùn)行)和動(dòng)態(tài)工具(執(zhí)行代碼以確定行為)。
可以使用一些靜態(tài)工具來(lái)查找潛在的緩沖區(qū)溢出問(wèn)題。很糟糕的是,沒(méi)有一個(gè)工具對(duì)一般公眾是可用的!許多工具做得一點(diǎn)也不比自動(dòng)化 grep 命令多,可以運(yùn)行它以找到源代碼中每個(gè)有問(wèn)題函數(shù)的實(shí)例。由于存在更好的技術(shù),這仍然是高效的方式將幾萬(wàn)行或幾十萬(wàn)行的大程序縮減到只有數(shù)百個(gè)“潛在的問(wèn)題”。(在以后的專欄文章中,將演示一個(gè)基于這種方法的、草草了事的掃描工具,并告訴您有關(guān)如何構(gòu)建它的想法。)
較好的靜態(tài)工具利用以某些方式表示的數(shù)據(jù)流信息來(lái)斷定哪個(gè)變量會(huì)影響到其它哪個(gè)變量。用這種方法,可以丟棄來(lái)自基于 grep 的分析的某些“假肯定”。David Wagner 在他的工作中已經(jīng)實(shí)現(xiàn)了這樣的方法(在“Learning the basics of buffer overflows”中描述;請(qǐng)參閱 參考資料),在 Reliable Software Technologies 的研究人員也已實(shí)現(xiàn)。當(dāng)前,數(shù)據(jù)流相關(guān)方法的問(wèn)題是它當(dāng)前引入了假否定(即,它沒(méi)有標(biāo)志可能是真正問(wèn)題的某些調(diào)用)。
第二類方法涉及動(dòng)態(tài)分析的使用。動(dòng)態(tài)工具通常把注意力放在代碼運(yùn)行時(shí)的情況,查找潛在的問(wèn)題。一種已在實(shí)驗(yàn)室使用的方法是故障注入。這個(gè)想法是以這樣一種方式來(lái)檢測(cè)程序:對(duì)它進(jìn)行實(shí)驗(yàn),運(yùn)行“假設(shè)”游戲,看它會(huì)發(fā)生什么。有一種故障注入工具 ― FIST(請(qǐng)參閱 參考資料)已被用來(lái)查找可能的緩沖區(qū)溢出脆弱性。
最終,動(dòng)態(tài)和靜態(tài)方法的某些組合將會(huì)給您的投資帶來(lái)回報(bào)。但在確定最佳組合方面,仍然有許多工作要做。
Java 和堆棧保護(hù)可以提供幫助
如上一篇專欄文章中所提到的(請(qǐng)參閱 參考資料),堆棧搗毀是最惡劣的一種緩沖區(qū)溢出攻擊,特別是,當(dāng)在特權(quán)模式下?lián)v毀了堆棧。這種問(wèn)題的優(yōu)秀解決方案是非可執(zhí)行堆棧。 通常,利用代碼是在程序堆棧上編寫,并在那里執(zhí)行的。(我們將在下一篇專欄文章中解釋這是如何做到的。)獲取許多操作系統(tǒng)(包括 Linux 和 Solaris)的非可執(zhí)行堆棧補(bǔ)丁是可能的。(某些操作系統(tǒng)甚至不需要這樣的補(bǔ)丁;它們本身就帶有。)
非可執(zhí)行堆棧涉及到一些性能問(wèn)題。(沒(méi)有免費(fèi)的午餐。)此外,在既有堆棧溢出又有堆溢出的程序中,它們易出問(wèn)題。可以利用堆棧溢出使程序跳轉(zhuǎn)至利用代碼,該代碼被放置在堆上。 沒(méi)有實(shí)際執(zhí)行堆棧中的代碼,只有堆中的代碼。這些基本問(wèn)題非常重要,我們將在下一篇專欄文章中專門刊載。
當(dāng)然,另一種選項(xiàng)是使用類型安全的語(yǔ)言,譬如 Java。較溫和的措施是獲取對(duì) C 程序中進(jìn)行數(shù)組邊界檢查的編譯器。對(duì)于 gcc 存在這樣的工具。這種技術(shù)可以防止所有緩沖區(qū)溢出,堆和堆棧。不利的一面是,對(duì)于那些大量使用指針、速度是至關(guān)重要的程序,這種技術(shù)可能會(huì)影響性能。但是在大多數(shù)情況下,該技術(shù)運(yùn)行得非常好。
Stackguard 工具實(shí)現(xiàn)了比一般性邊界檢查更為有效的技術(shù)。它將一些數(shù)據(jù)放在已分配數(shù)據(jù)堆棧的末尾,并且以后會(huì)在緩沖區(qū)溢出可能發(fā)生前,查看這些數(shù)據(jù)是否仍然在那里。這種模式被稱之為“金絲雀”。(威爾士的礦工將 金絲雀放在礦井內(nèi)來(lái)顯示危險(xiǎn)的狀況。當(dāng)空氣開始變得有毒時(shí),金絲雀會(huì)昏倒,使礦工有足夠時(shí)間注意到并逃離。)
Stackguard 方法不如一般性邊界檢查安全,但仍然相當(dāng)有用。Stackguard 的主要缺點(diǎn)是,與一般性邊界檢查相比,它不能防止堆溢出攻擊。一般來(lái)講,最好用這樣一個(gè)工具來(lái)保護(hù)整個(gè)操作系統(tǒng),否則,由程序調(diào)用的不受保護(hù)庫(kù)(譬如,標(biāo)準(zhǔn)庫(kù))可以仍然為基于堆棧的利用代碼攻擊打開了大門。
類似于 Stackguard 的工具是內(nèi)存完整性檢查軟件包,譬如,Rational 的 Purify。這類工具甚至可以保護(hù)程序防止堆溢出,但由于性能開銷,這些工具一般不在產(chǎn)品代碼中使用。
結(jié)束語(yǔ)
在本專欄的上兩篇文章中,我們已經(jīng)介紹了緩沖區(qū)溢出,并指導(dǎo)您如何編寫代碼來(lái)避免這些問(wèn)題。我們還討論了可幫助使您的程序安全遠(yuǎn)離可怕的緩沖區(qū)溢出的幾個(gè)工具。表 1 總結(jié)了一些編程構(gòu)造,我們建議您小心使用或避免一起使用它們。如果有任何認(rèn)為我們應(yīng)該將其它函數(shù)加入該列表,請(qǐng)則通知我們,我們將更新該列表。
函數(shù) |
嚴(yán)重性 |
解決方案 |
gets |
最危險(xiǎn) |
使用 fgets(buf, size, stdin)。這幾乎總是一個(gè)大問(wèn)題! |
strcpy |
很危險(xiǎn) |
改為使用 strncpy。 |
strcat |
很危險(xiǎn) |
改為使用 strncat。 |
sprintf |
很危險(xiǎn) |
改為使用 snprintf,或者使用精度說(shuō)明符。 |
scanf |
很危險(xiǎn) |
使用精度說(shuō)明符,或自己進(jìn)行解析。 |
sscanf |
很危險(xiǎn) |
使用精度說(shuō)明符,或自己進(jìn)行解析。 |
fscanf |
很危險(xiǎn) |
使用精度說(shuō)明符,或自己進(jìn)行解析。 |
vfscanf |
很危險(xiǎn) |
使用精度說(shuō)明符,或自己進(jìn)行解析。 |
vsprintf |
很危險(xiǎn) |
改為使用 vsnprintf,或者使用精度說(shuō)明符。 |
vscanf |
很危險(xiǎn) |
使用精度說(shuō)明符,或自己進(jìn)行解析。 |
vsscanf |
很危險(xiǎn) |
使用精度說(shuō)明符,或自己進(jìn)行解析。 |
streadd |
很危險(xiǎn) |
確保分配的目的地參數(shù)大小是源參數(shù)大小的四倍。 |
strecpy |
很危險(xiǎn) |
確保分配的目的地參數(shù)大小是源參數(shù)大小的四倍。 |
strtrns |
危險(xiǎn) |
手工檢查來(lái)查看目的地大小是否至少與源字符串相等。 |
realpath |
很危險(xiǎn)(或稍小,取決于實(shí)現(xiàn)) |
分配緩沖區(qū)大小為 MAXPATHLEN。同樣,手工檢查參數(shù)以確保輸入?yún)?shù)不超過(guò) MAXPATHLEN。 |
syslog |
很危險(xiǎn)(或稍小,取決于實(shí)現(xiàn)) |
在將字符串輸入傳遞給該函數(shù)之前,將所有字符串輸入截成合理的大小。 |
getopt |
很危險(xiǎn)(或稍小,取決于實(shí)現(xiàn)) |
在將字符串輸入傳遞給該函數(shù)之前,將所有字符串輸入截成合理的大小。 |
getopt_long |
很危險(xiǎn)(或稍小,取決于實(shí)現(xiàn)) |
在將字符串輸入傳遞給該函數(shù)之前,將所有字符串輸入截成合理的大小。 |
getpass |
很危險(xiǎn)(或稍小,取決于實(shí)現(xiàn)) |
在將字符串輸入傳遞給該函數(shù)之前,將所有字符串輸入截成合理的大小。 |
getchar |
中等危險(xiǎn) |
如果在循環(huán)中使用該函數(shù),確保檢查緩沖區(qū)邊界。 |
fgetc |
中等危險(xiǎn) |
如果在循環(huán)中使用該函數(shù),確保檢查緩沖區(qū)邊界。 |
getc |
中等危險(xiǎn) |
如果在循環(huán)中使用該函數(shù),確保檢查緩沖區(qū)邊界。 |
read |
中等危險(xiǎn) |
如果在循環(huán)中使用該函數(shù),確保檢查緩沖區(qū)邊界。 |
bcopy |
低危險(xiǎn) |
確保緩沖區(qū)大小與它所說(shuō)的一樣大。 |
fgets |
低危險(xiǎn) |
確保緩沖區(qū)大小與它所說(shuō)的一樣大。 |
memcpy |
低危險(xiǎn) |
確保緩沖區(qū)大小與它所說(shuō)的一樣大。 |
snprintf |
低危險(xiǎn) |
確保緩沖區(qū)大小與它所說(shuō)的一樣大。 |
strccpy |
低危險(xiǎn) |
確保緩沖區(qū)大小與它所說(shuō)的一樣大。 |
strcadd |
低危險(xiǎn) |
確保緩沖區(qū)大小與它所說(shuō)的一樣大。 |
strncpy |
低危險(xiǎn) |
確保緩沖區(qū)大小與它所說(shuō)的一樣大。 |
vsnprintf |
低危險(xiǎn) |
確保緩沖區(qū)大小與它所說(shuō)的一樣大。 |
在我們急匆匆講述這些基礎(chǔ)知識(shí)時(shí),到現(xiàn)在為止,已經(jīng)遺漏了一些緩沖區(qū)溢出很酷的細(xì)節(jié)。在下幾篇專欄文章中,我們將深入這臺(tái)“引擎”的工作,并給它加點(diǎn)黃油。我們將詳細(xì)地了解緩沖區(qū)溢出的工作原理,甚至還會(huì)演示一些利用代碼。
參考資料
作者簡(jiǎn)介
 |
|
 |
Gary McGraw是 Reliable Software Technologies 負(fù)責(zé)企業(yè)技術(shù)的副總裁,該公司位于美國(guó)弗吉尼亞州杜勒斯(Dulles)。他從事咨詢服務(wù)和研究工作,幫助決定技術(shù)研究和開發(fā)方向。McGraw 在 Reliable Software Technologies 從一個(gè)研究科學(xué)家做起,從事軟件工程和計(jì)算機(jī)安全性方面的研究。他擁有印第安那大學(xué)認(rèn)知科學(xué)和計(jì)算機(jī)科學(xué)雙博士學(xué)位,弗吉尼亞大學(xué)的哲學(xué)學(xué)士學(xué)位。他為技術(shù)刊物撰寫了 40 余篇經(jīng)同行審查的文章,擔(dān)任過(guò)主要的電子貿(mào)易供應(yīng)商(包括 Visa 和 Federal Reserve)的顧問(wèn)職務(wù),并在空軍研究實(shí)驗(yàn)室、DARPA、國(guó)家科學(xué)基金會(huì)以及 NIST 的高級(jí)技術(shù)項(xiàng)目贊助下?lián)纹涫紫{(diào)研員。
McGraw 是移動(dòng)代碼安全性方面著名的權(quán)威人士,并且與普林斯頓的教授 Ed Felten 合作撰寫了“Java Security: Hostile Applets, Holes, & Antidotes”(Wiley, 1996)以及“Securing Java: Getting down to business with mobile code”(Wiley, 1999)。McGraw 和 RST 創(chuàng)始人之一、首席科學(xué)家 Dr. Jeffrey Voas 一起編寫了“Software Fault Injection: Inoculating Programs Against Errors”(Wiley, 1998)。McGraw 定期為一些受歡迎的商業(yè)出版物撰稿,而且其文章經(jīng)常在全國(guó)出版的文章中所引用。 |
 |
|
 |
John Viega是一名高級(jí)副研究員,Software Security Group 的共同創(chuàng)始人,并擔(dān)任 Reliable Software Technologies 的高級(jí)顧問(wèn)。他是 DARPA 贊助的開發(fā)標(biāo)準(zhǔn)編程語(yǔ)言安全性擴(kuò)展的首席調(diào)研員。John 已撰寫了 30 余篇涉及軟件安全性和測(cè)試領(lǐng)域的技術(shù)性文章。他負(fù)責(zé)在主要網(wǎng)絡(luò)和電子商業(yè)產(chǎn)品中查找一些眾所周知的安全性弱點(diǎn),包括最近在 Netscape 安全性中的缺陷。他還是開放源碼軟件社區(qū)的重要成員,編寫過(guò) Mailman、GNU Mailing List Manager 以及最近發(fā)布的 ITS4(一種在 C 和 C++ 代碼中查找安全性弱點(diǎn)的工具)。Viega 擁有弗吉尼亞大學(xué)計(jì)算機(jī)科學(xué)碩士學(xué)位。 | |