歡迎您光臨本站 註冊首頁

安全編程詳解v1.00

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  v安全編程詳解v1.00

翻譯:By xundi@xfocus.org --> 此原文是來自www.securityfocus.com中的Secure Programming v1.0,本人建議能讀原文讀原文,如果你英語實在不過關,就讀此文吧,並且感謝你能花不少時間來看這篇我敲了很長時間的文章。
--------------------------------------------------------------------------------
0.0 目的

1.0 一般情況
1.1 緩衝溢出

1.1.1 strcpy
1.1.2 strcat
1.1.3 sprintf
1.1.4 gets
1.1.5 scanf, sscanf, fscanf
1.1.6 memcpy
1.1.7 C++

1.2 用戶數據合法性檢查
1.3 DNS名字合法性檢查
1.4 返回值(Return Values)
1.5 設備驅動程序(Device Drivers)

2.0 UNIX 操作系統

2.0.1 Real UID(實際UID), effective UID(有效UID), 和saved UID(保留UID)
2.0.2 setuid 和setgid 程序

2.1環境變數
2.2默認文件許可權 (umask)
2.3 不安全的使用臨時文件

2.3.1 tmpfile()
2.3.2 mkstemp()
2.3.3 mktemp()
2.3.4 一些額外的解決方法

2.4 文件競爭條件問題
2.5 chroot環境實現
2.6 丟棄權利

2.6.1 In a network service
2.6.2 In a local setuid program

2.7 Generating Random Numbers

2.7.1 Linux
2.7.2 OpenBSD

2.8 Invoking Child Processes
2.9 Resource Limitations
2.9.1 Core files


3.0 Windows Operating Systems

3.1 Access Control Lists
3.2 Registry Keys
3.3 Files
3.4 Generating Random Numbers


4.0 Designing Protocols

4.1 Authentication
4.2 Confidentiality
4.3 Pitfalls

4.3.1 Replay Attacks


5.0 Tools

5.1 Unix Tools
5.2 Windows Tools


6.0 References and Resources

6.1 Contributors

本文目的

此文檔的目的是為了使讀者了解怎樣安全編程,幫助用戶能很快的運行這些技術應用於到編程裡面去。我們這裡敘述了關於設計安全應用程序的內容,如軟體產品實現中一些容易犯的錯誤和缺陷。
在處於繁忙緊張的軟體開放周期今天,只有少量或者沒有安全經驗編程的人參加開發程序是很常見的,個人的在對應用程序中實現佔有重要地位與安全很有關係,不光光個人會出現錯誤,一般組織,團體,為了短時間裡完成產品開發,也很少會重視到安全問題,最後受到更多損失的還是團體。一個基本的原則,安全問題存在與任何類型的應用程序,不光光是一般應用程序,連一些有關安全的應用程序也會存在某些安全問題(如ISS開發的安全產品中也存在緩衝溢出等問題)。

漏洞大多數是會導致程序強制操作一些不同與原來作者本來意圖的行為,而這些缺陷和錯誤一般遵循著的固定的樣式,我們這文章的目的是示範怎樣組織它們,多數情況下,我們的思路是考慮」假如這樣然後會怎樣?」和仿效攻擊者的思路。
對於計算機軟體可靠性方面已經有不少方面的研究,威斯康星大學研究所對許多不同UNIX版本操作系統基本工具程序進行過測試。在他們的「Fuzz Revisited: A Re-examination of the Reliability of UNIX Utilities and Services」一文中,研究人員通過傳遞隨機數據到程序中對這些程序進行過測試,在1990年開始的測試過程中,發現普通的程序超過」%40」和X-WINDOWS程序存在問題而導致崩潰或者掛起,不過在1995年的測試一中,同樣的失敗率得到一定程度的改良,不過仍舊在18-23%之間,不過發現商業類型操作系統比一些開放代碼的系統如LINUX高很多,一般在2倍左右,GNU軟體的故障率最低,只有7%。
而研究表明問題一般存在與傳遞隨機的數據往往導致錯誤的產生,而這些錯誤可以通過構建精確的結構數據而利用並導致惡意的結果(如ROOT權利的獲得)。
This document is currently maintained by Oliver Friedrichs of@securityfocus.com. This document is a work in progress and is expanded upon by discussions that have taken place on the SECPROG mailing list at http://www.securityfocus.com/forums/secprog/.

1.0 一般情況
這部分章節討論的安全編程技術可以同時應用於UNIX和WINDOW操作系統,涵蓋的主題可以在多種語言和操作系統的得到應用,這些技術能在絕大多數計算機上應用的編程的定例和概念。

1.1 緩衝溢出

至今為止最通常的一種安全漏洞—緩衝溢出在今天的用種應用程序中存在,不過這個問題不是新問題,相反它在前10年前就存在於一些操作系統和應用程序,而1近10年來此類型問題的發現和在重要軟體上被利用的頻率飛快的增加,這不是問題不存在,而在於這種攻擊的理念的性質而不被以前多數用戶感興趣,今天隨著極具機械式的怎樣利用這些問題的教育和一些可以說「異端「人的增加,導致使用個技術簡單話。
此部分目的不是描述怎樣挖掘一個有緩衝溢出的漏洞,而是理解這種類型問題的重要性和運行這種程序會導致怎樣的嚴重後果。
緩衝溢出出現在當一些數據拷貝到內存區域中產生,如果內存的緩衝區不能夠容納這些數據,到拷貝成功的時候,目標內存的邊界部分就會被覆蓋。程序中的變數可以被在程序棧(stack)中也可以在程序堆(heap)中,因此我們也可以常聽到這些字彙,棧溢出和堆溢出,相比較這兩個溢出類型,棧溢出的利用多數情況下會比較容易點。
讓我們看看一段存在緩衝溢出錯誤的源代碼:
void function(char *str)
{
char buffer[16];
strcpy(buffer,str);
}

void main()
{
char large_string[256];
int i;

for( i = 0; i < 255; i++)
large_string[i] = 'A';

function(large_string);
}
在上面的程序中,func()函數拷貝一有255位元組組成的字元串到只有16位元組大小的緩衝中,並且由於使用了strcpy()而沒有檢查位元組數,這樣你編譯運行這個程序的話,運行的時候將導致段衝突。
255個字元串拷貝到緩衝區時,緩衝在開頭16個位元組填充后就被填滿,這樣,多出來的字元串將覆蓋堆棧中的其他變數,包括函數的返回地址,這返回地址是指當前函數返回時程序所需繼續執行的內存地址,通過修改這個地址,攻擊者可讓程序在不同的位置後續執行,而如果在剛才遞交給緩衝中的數據帶有指令代碼,且被覆蓋的地址又切切指向這段代碼,此代碼就會被執行.
通過使用strncpy()函數代替,就能比較安全的拷貝字元串,因為它只會將大小同目標緩衝區的數據拷貝.
現在許多有緩衝溢出傾向的函數都存在相對安全的副本函數,它們都通過傳遞大小參數來限制多少數據被調用

 strcpy() 此函數沒有執行任意長度的確認,而使用strncpy()函數可以限制長度,當使
用strncpy()的時候,你需要含蓄地以NULL終結字元串,因為有NULL終
止符暗示定義源緩衝區的大小

不正確情況 正確的情況

void func(char *str) void func(char *str)
{ {
char buffer[256]; char buffer[256];
strcpy(buffer, str); strncpy(buffer, str, sizeof(buffer) -1);
return; return;
} }

strcat() 此函數類似與strcpy(),也不檢查字元串的長度,使用strncat()可以很好的限制
被拷貝的長度,strncat()函數只會追加定義在大小參數中的長度,NULL終止符將終止字
符串.

不正確情況 正確的情況

void func(char *str) void func(char *str)
{ {
char buffer[256]; char buffer[256];
strccat(buffer, str); strncat(buffer, str, sizeof(buffer) -1); return; return;
} }

sprintf() 此函數用來格式化字元串變數,是很容易產生緩衝溢出的地方,前幾個月出現
過很多嚴重的緩衝溢出,如wuftp,就是使用了這個函數的緣故.使用snprintf()
可以很好的限制拷貝到緩衝中的列印數據長度,snprintf()函數返回要傳遞過
去的數據字元總數,如果它大於能傳遞位元組大小的值,那麼沒有任何數據寫到
緩衝中,用戶必須檢查snprintf()的值以保證要被列印的緩衝.

不正確情況 正確的情況

void func(char *str) void func(char *str)
{ {
char buffer[256]; char buffer[256];
sprintf(buffer,」%s」, str); if (snprintf(target, sizeof(target) - 1, "%s", return; string) > sizeof(target) - 1)
} /* 緩衝發生 */
return;
}
gets 此函數天生就存在漏洞,你在應用程序中應該永遠不要使用,一般在你編譯的時候
也會被警告,gets()沒有規定任意長度,永遠將導致緩衝溢出,它從標準輸入中讀取數
據並一直到換行符或者EOF標識接收到,所讀的數據全部填充到定義的緩衝,並不
檢查任何長度.

不正確情況 正確的情況

void func(char *str) void func(char *str)
{ {
char buffer[256]; char buffer[256];
gets(buffer, str); fgets(buffer, sizeof(buffer) ?1,stdin);
return; return;
} }
而上面的fgets()函數用來代替不安全的gets()函數,此函數會至多讀取』sizeof(buffer) ? 1』
的文件流 .當然你中途讀取到EOF或者換行符也會停止.

scanf(), 在使用這些函數的時候你需要小心讀取要放到固定長度緩衝區的數據,你須確保規sscanf(), 定要讀到緩衝區的數據長度,下面很好的演示了有問題和安全的兩個版本:
fscanf()
有漏洞的版本 安全的版本
char buffer[256]; char buffer[256];
int num; int num;
num = fscanf(stdin, "%s", buffer); num = fscanf(stdin, "%255s", buffer);
你可以看到上面安全的那個示例,規定了只能讀取255個字元到規定的緩衝中,如果類似
上面所示第一個示例,就會無限制的讀取,並導致函數的堆棧被破壞.

Memcpy() 由於不安全的使用memcpy()會導致不少溢出,當規定了長度的memcpy()函數
可以受外界操作的時候,就可能發生,所以保證拷貝到內存結構中的數據不大於
規定大小將非常重要,下面一個示例將說明緩衝是怎樣產生的:
unsigned long copyaddress(struct hosten *hp) {
unsigned long address;

memcpy(&address, hp->h_addr_list[0], hp->h_length);
}
上面的示例來自與一個出現在BIND (Berkeley 網路名字守護程序)上的實際
漏洞,在這裡我們為了教育目的簡單花了,上面的函數拷貝hp->h_length位元組
到』address』變數中(4位元組),在正常情況下, 因為是網路地址hp->h_length一般
都是4位元組,但是,攻擊者確可以操作利用h_length變數,如果他偽造一個假
的DNS回應,他就可以提供更長的地址通過hp-h_addr_list變數傳遞,這將導致
超過4個位元組大小拷貝到』address』變數,溢出變數,拷貝多的數據到堆棧.

所以你確保在執行這種操作前檢查長度,如:

unsigned long copyaddress(struct hosten *hp) {
unsigned long address;

if (hp->h_length > sizeof(address))
return 0;

memcpy(&address, hp->h_addr_list[0], hp->h_length);

return address;
}
此漏洞在BIND系統中已經修補.
C++ C++語言存在一些獨特的額外的問題,如下面的例子會導致程序產生緩衝溢出,極類
似C中的gets()函數.
char buf[128];
cin >> buf;
上面函數中在讀字元串的時候沒有長度檢查,需確保使用cn.width()函數來規定最
大緩衝大小.

1.2 用戶數據合法性檢查

在某些情況下需要確認用戶輸入,並去除不合法的字元或者數據.讀取用戶名來驗證就是一個合法的例子,要儘可能的剔除我們所能知道的不合法的字元,如高位字元,空格或者數字.比較好的方法就是簡單的剔除所有除了自己需要的數據,這樣就不需要我們來猜測所有危險的字元,而只要我們知道安全的字元.這種錯誤一般在使用SHELL命令時傳遞數據到第二個程序的時候發生.

這種問題最突出的例子是發生在基於WEB的CGI應用程序,如phf,這個CGI程序在以前NCSA和APACHE WEB伺服器中是默認安裝的,phf程序通過popen()函數來傳遞數據到程序中剔除了一些已知的不良的字元,但是,它遺漏了其中一個字元=換行符(\n),其中在HTML查詢中是通過%0a代替的,通過使用這個字元把數據傳遞給程序的時候,攻擊者可以在遠程主機中執行任意命令,當在遠程主機使用SHELL解程序解析的時候,換行符扮演了命令分隔符,把分行符前字元串作為一個命令,而把換行符後面的字元串作為一個新命令,通過請求下面的ULR,就可能在主機上執行」cat /etc/passwd」的命令,而攻擊者就可以通過WEB服務程序的響應得到密碼文件內容:

/cgi-bin/phf?Qalias=hell%0acat%20/etc/passwd%0a

在這個漏洞被發現之後,在一般的庫函數中做了修補來清除輸入,如果在換行符後面
增加了字元串將被截斷.這樣的好情況持續了一段時間直到有人開發了
BASH(Bourne Again Shell),這個很常用的UNIX SHELL解釋程序卻允許ASCII碼255
作為命令分隔符,導致了相同的攻擊產生,並導致不少操作系統受到攻擊,因為這個
SHELL解釋程序在LINUX下是默認安裝的.要正確修補這個問題,最好就是只允許
已知的沒有危害的字元輸入.

不正確情況 正確的情況

#define BAD "/ ;[]<>&\t" #define OK "abcdefghijklmnopqrstuvwxyz\ BCDEFGHIJKLMNOPQRSTUVWXYZ\
1234567890_-.@";

char *query() char *query()
{ {
char *user_data, *cp; char *user_data, *cp;
/* 獲取數據 */ /* 獲取數據 */
user_data = getenv("QUERY_STRING"); user_data = getenv("QUERY_STRING");
/* 剔除不良數據 */ /* 去除除了良性數據的所有數據*/
for (cp = user_data; *(cp += strcspn(cp, BAD)); ) for (cp = user_data; *(cp += strspn(cp, OK));)
*cp = '_'; *cp = '_';
return user_data; return user_data;
} }

在不正確的例子中,只是把認為不良的數據中查詢字元串中剔除,而可能存在未知的隱
患字元,正確的方法就是去掉所有字元,只保留一些我們知道的正確的數據.

1.3 DNS名字合法性檢查
這裡建議你永遠不要在任何類型的應用程序中使用DNS名字,唯一安全的認證機制
就是使用加密.但是你如果必須使用到DNS,很重要的一條你必須記住校驗主機名.

這裡假設你希望對你寫的一特殊網路服務限制使用單一授權的主機名,當連接接受到
的時候我們從accept()調用獲得連接地址,捅咕這個地址,我們可以判斷連接主機的
DNS名字,我們把這個地址傳遞到一確認的函數.

不正確情況 正確的情況






















在不正確的例子中,對傳遞的IP地址進行反向查詢的.一旦反向查詢完成,被查詢的主機名會和我們」允許」主機名做比較,如果它們匹配,認證成功,這裡就存在漏洞可以導致攻擊者繞過這個安全機制,如果合法用戶連接,將發生下面的情況:

a: 用戶從1.2.3.4的合法地址連接
b: 服務程序對1.2.3.4執行反向查詢,會對1.2.3.4網路中的DNS伺服器發送一查詢,並
接收到為client.allowed.com正確的響應.
C: 服務程序進行比較,並發現client.allowed.com是允許的主機名.

攻擊者可以通過下面的步驟利用裡面的漏洞:

a: 攻擊者從合法地址5.6.7.8連接
b: 攻擊者完全控制它自己C段網路中的DNS伺服器,並修改它的記錄使任何對地址
5.6.7.8的查詢會返回client.allowed.com的主機名.
C: 服務程序對5.6.7.8執行反向查詢,對5.6.7.8網路中的DNS伺服器發送查詢,而這個
DNS伺服器是攻擊者控制的,並接收5.6.7.8的正確響應是client.allowed.com.
D: 服務程序進行比較,發現client.allowed.com是允許的主機名.

正確的例子中執行對主機名做一正向查詢來保證主機名的解析是正連接著的IP地址.由於主機名可以有多個IP地址,它們將全部被檢查,這個檢查就會讓攻擊者執行上面所述的攻擊難度大大增加,這時攻擊者還需要控制服務allowed.com域的DNS伺服器,而這個伺服器不是它他們自己的網路上的.他們必須先滲透遠程主機的網路來控制DNS伺服器,這樣使控制困難大大增加.

1.4 返回值(Return Values)

檢查函數返回的值看起來很符合邏輯的,但在某些情況下,它往往不會注意到,在一些安全性比較重要的應用程序中檢查對所有函數的返回值是非常重要的,讓我們看看下面的例子會帶來什麼樣的安全問題:

char maketemp()
{
char *name = 「/tmp/tempfile」;

creat(name, 0644);
chown(name, 0, 0);
return name;
}

在上面的情況下對沒有檢查函數的返回值是很大的錯誤,讓我們來看例子中相關的返回值.如果攻擊者拷貝UNIX SHELL -/bin/sh為/tmp/tempfile,creat()函數會不成功執行,這是由於文件已經存在, creat()的返回值沒有被檢查,函數會很高心的運行下去,這時攻擊者安排文件setuid,因為不檢查creat()返回值,程序下續命令就會改變文件的屬主為ROOT,並遞送攻擊者一個setuid ROOT SHELL,提供超級用戶訪問.不過有些操作系統在chown被執行的時候重新設置setuid來防止這種情況,我們上面知識簡單的用來演示一下.

永遠確保年檢查返回值的成功與否,在UNIX中,當系統調用失敗的時候,程序的全局errno變數將被設置來指示出錯誤的原因.

1.5 設備驅動程序(Device Drivers)
設備驅動程序介入了新的安全問題,設備驅動程序擁有唯一特性,因為它們在操作系統內核運行,這意味著設備驅動程序完全控制著操作系統和操作系統內存,設備驅動程序和一般的系統進程有區別,它能訪問所有系統內存,如果設備驅動程序中存在缺陷湖者錯誤將導致整個操作系統故障和崩潰,在UNIX下表現為內核出錯,而在WINDOWS操作系統下,表現為藍屏.一般進程中有這種問題將導致進程崩潰.
設備驅動程序通常把介面或者API」暴露」在用戶空間中,如果沒有對其採取適當的預防將導致不可預期的影響.如果系統調用-即把操作系統內核部分的功能」暴露」給用戶,也會存在這個問題,不光光你自己你對操作系統增加新的系統調用會靠不住,有時在操作系統中默認的標準系統調用也會出現這種問題.
當寫有外界輸入的設備驅動程序的時候,你必須-一定的很小心的對所有輸入變數進行凈化處理和檢查,下面的情況你需要預防輸入數據確認:
a:地址檢查-當指向結構或者內存位置的指針通過ioctl()調用傳遞給設備驅動程序的時
候,必須確認只很必須有效而不是指向非法內存,如果一個不合法的指針被傳遞和引用,
結果往往造成操作系統崩潰.
B: 符號/無符號不匹配 ? 這個問題在設備驅動程序和系統調用執行沒有正確處理無符
號變數時普遍存在.如果用戶傳遞很大的數字到函數調用,並希望作為無符號數字處
理,但是有些不完善的函數會把它當作一有符號的數字,這樣這個值將被轉換為不可預
期的負值,如果把這個數字作為偏移量或者內存中的指數,很嚴重的問題將發生.

一個有趣的歷史性錯誤例子就是多年前[4]基於BSD控制台驅動程序,驅動程序在內存中包含一屏幕緩衝,但沒有正確驗證特殊的鍵值,如backspace(倒退)鍵,用戶可以用backspace(倒退)鍵返回到屏幕開始處,然後繼續操作backspace(倒退)鍵知道屏幕緩衝之外,導致覆蓋緩衝前面的內存數值.

2.0 UNIX操作系統
這部分我們討論應用在基於UNIX操作系統的安全編程問題,UNIX家族的操作系統包含的一些安全機制不會出現在其他如WINDOWS操作系統上,這些不同處是軟體工作者在開發基於UNIX軟體中需要注意的.

2.0.1 Real UID(實際UID), effective UID(有效UID), 和saved UID(保留UID)

所有運行在UNIX操作系統上的程序在特殊系統用戶和組權利下運行,每個進程擁有自己的一系列信任內容,它是操作系統用來執行訪問驗證檢查.文件,目錄和系統資源的許可權是基於使用它們的用戶分配的.操作系統內核自身是執行訪問驗證檢查來判斷一進程是否有權利訪問資源的實體.了解信任系統是怎樣工作非常重要,許多安全漏洞是沒有完全理解這些內容而出現的.
當前POSIX操作系統對每個進行採用3類信任方式:

Real UID 和 Real GID :實際用戶ID和實際組ID作為進程運行,只有進程以超級用戶
或如果用戶ID或組ID它們想變為相同的有效用戶ID或組ID(見下面)才能改變運行著的uid和gid一進程要想改變運行中
的uid和gid.

有效UID和有效GID 每個進程有一個有效用戶ID,在通常的環境中最初是設置成和
實際用戶ID一樣的值,但是當一setuid文件被執行以後,有效用戶ID屬於文件的屬主,因此在執行setuid root程序中,進程的有效uid會變為0(root),而此時實際uid會仍舊是用戶UID.這是系統調用用於判斷進程是否有足夠的權利訪問一資源.

保留UID和保留GID 每個進程有一個保留用戶ID和組ID,這是當setuid或者setgid
被執行的時候設置有效用戶ID和有效組ID的,這些保留ID
允許進程臨時丟棄權利(通過設置有效uid或gid給一個權利低
的用戶)並在以後重新獲取這些權利使用.

注意:沒有系統調用可以直接獲得或設置保留uid或gid,由於保留ID在程序執行段相等於有
效ID,你可以通過使用geteuid() 和getegid()來確定保留ID

2.0.2 SetUID 和SetGID程序

通常情況下一用戶執行程序時,程序以當前用戶的屬性來運行,而UNIX操作系統支持用戶以文件屬主或組的屬性來執行,這在文件設置了」setuid」和」setgid」位時發生.就是說當文件設置了」setuid」位,程序可以以文件屬主的權利運行,而文件被設置了」setgid」,程序就可以以文件屬組的權利運行,這兩個屬性可以同時在一個文件上設置.請看下例:
-rwsr-sr-x 1 root wheel 32045 Jun 6 10:15 program
上面的」s」表示程序設置了」setuid」和」setgid」,不管程序是誰運行,它可以以root用戶和wheel組來執行,這裡相關了一個安全問題,如果程序有一個缺陷,就可能導致把一個普通用戶提升為root用戶.
Setuid和setgid程序在此文中直接影響所有問題,下面是一些必須採用的常規方法:
1,儘可能縮小程序的權利.
2,當你完成需要這些權利的任務時馬上丟棄.
3,定義一個健康的API,它必須在涵蓋程序內部所有功能,所謂用戶介面提供給用戶,盡可
能檢查這個API的安全問題.
4,不要信任任何外部輸入,確保你使用他們之前驗證和凈化數據.

2.1 環境變數
環境變數類似命令行選項,提供了一種提供任何數據給程序的方法,環境變數的安全問題在本地系統程序中超過網路服務,因為網路服務一般情況下不接受環境參數(少數除外).環境變數問題可以造成許多不合法的程序響應:
1,緩衝溢出 ? 此問題可其他情況下一樣,沒有處理好對其的檢查,下面是一個常見的類型:

char *s, buf[128];

if (!(s = getenv(「HOME」)))
return ?1;

strcpy(buf, s);

上面的函數中,沒有考慮到要獲取的」HOME」環境變數的長度,超過128位元組參數提供給緩衝就造成溢出.
一個嚴重的問題在FREEBSD系統中發現過,Thomas Ptacek發現在FREEBSD的C實時運行庫(C runtime library)中對環境變數處理有問題,而C實時運行庫(C runtime library)靜態連接了系統每一個程序,結果造成系統上每個setuid/setgid存在緩衝溢出.
下面一段話是引用1997年2月Ptacek發表在Bugtraq上的:
「在FREEBSD2.1.5上存在一個嚴重的安全問題, C實時運行庫(C runtime library)可讓任
何人控制進程的環境變數而導致執行任意代碼,所有SUID程序都受此漏洞影響.

問題存在」 startup_setrunelocale()",如果部分環境變數被設置,會直接拷
貝」PATH_LOCALE」的值到1024緩衝中,攻擊者只需簡單的把機器碼和虛擬內存地址
插入到」PATH_LOCALE」中,就可以啟動locale處理和運行SUID程序.

2,繼承性問題 ? 環境變數會被繼承到通過主進程派生的子進程,因此當傳遞環境變數到
子進程時必須很小心.你可以使用execle() 和execve()系統調用來運行程序並定義它的
環境變數.

2.2 默認屬性(umask)

當你建立文件或者運行程序時,它建立一系列默認的文件屬性,這些屬性通過設置進程的umask來規定,它可以從登陸SHELL或者當前進程父進程中繼承下來.很重要的一點你必須確保文件建立時沒有不安全的屬性,不允許未授權用戶訪問他們.

Umask設置可以通過使用umask()庫調用來調整,它接受設置位作參數,這些位用來在建立的文件中清除相關聯的位,如:
umask(0) results in -rw-rw-rw-

上面沒有清楚新建立文件的任何位.
umask(022) results in -rw-r--r--

上面部分清除了文件屬性中組和其他人的可寫位.

umask(066) results in -rw-------
上面例子清除了組和其他人的讀和寫位.

2.3 不安全的使用臨時文件
許多漏洞發生在程序訪問知名或可猜測文件上,一個程序可能包含這樣一個漏洞:它在文件臨時目錄中打開文件,盲目的寫數據到文件中,通過利用符號連接,攻擊者經常可以重定向數據到其他文件中,這種類型的攻擊往往發生在下面2個情況下:

1,一個有特權的程序可以被攻擊者執行.這個有特權的程序在系統上以超級用戶在系統臨時目錄中打開了一文件:
/tmp/program.temp
通過在執行前建立一個符號連接,攻擊者可以把這個文件指向其他特殊文件:
ln ?s /etc/passwd /tmp/program.temp
當程序寫數據到文件的時候,由於符號連接的作用,實際上寫到了/etc/passwd中,如果寫
的數據能被攻擊者控制,那就可能獲得ROOT的權利

2,攻擊者期望系統上其他用戶執行一已經知道有臨時文件處理問題的非特權程序,攻擊
者建立如下連接:
ln ?s /etc/passwd /tmp/program.temp
並等待其他用戶執行程序,密碼文件可以被寫.

上面兩種情況主要區別在於第一個情況,程序設置了setuid root而以超級用戶執行,而第
個情況是利用其他用戶的權利來執行.

對這些問題有幾種解決方法:

1,不要在/tmp建立臨時文件.
2,如果要建立臨時文件,其名字必須隨機或者不可猜測.
3,利用一系統提供的介面(mkstemp())來建立臨時文件.

2.3.1 tmpfile()

tmpfile()函數可以建立臨時文件並返回一打開的文件句柄給文件流,tmpfile()可以避免在產生臨時文件名和建立文件時產生競爭條件問題,tmpfile()函數描述如下:

FILE *tmpfile(void);
在許多操作系統中,tmpfile()會使用mkstemp()函數來獲得和建立臨時文件名,然後在unlink()文件,再fdopen()文件描述符來返迴流句柄.

此函數的好處
1,臨時文件可以安全的建立.
2,避免在產生臨時文件名和打開這個文件之間的環境競爭.
3,臨時文件一旦建立變不能連接,防止文件在以後被任何人訪問和打開-一允許tmpfile()
返回的流(stream)才能訪問文件.

此函數的約束之處:

1,用戶不能控制文件建立的位置,默認情況在在/var/tmp或者/tmp.
2,文件突然被設置為不能連接(unlinked),因此它不能被另一個進程打開.
3,當文件描述符關閉的時候,文件就消失(因為它被設置unlinked),所有數據丟失.

2.3.2 mkstemp()
mkstemp()函數建立臨時文件,返迴文件描述符給文件,mkstemp()避免在產生臨時文件名和建立文件之間產生環境競爭問題.mkstemp()函數描述如下:

int mkstemp(char *template);

提供給mkstemp()函數的template提供了臨時文件的路徑,其中template是一系列X結尾,必須由隨機值填充來建立隨機的臨時文件名,如:

fd = mkstemp(「/tmp/tempfileXXXXXX」);

當使用這個函數的時候,你需要保證X的尾數至少為6位,有些系統支持超過6為以增加隨機文件名.

此函數的好處:
1,臨時文件可以安全的被建立,並返回時打開文件描述符給文件.
2, 避免在產生臨時文件名和打開這個文件之間的環境競爭.
3,一些操作系統選用運行進程的ID來產生文件名,這造成了文件名可猜測,可是這個函數仍舊可以對環境競爭問題免疫.

此函數的約束之處:

1,此文件對於其他進程可見,並且不是為unlinked狀態.
2,如果文件許可權設置不安全,其他進程可以查看或者修改文件內容.

2.3.3 mktemp()
mktemp()函數用來產生唯一文件名而沒有實際建立文件,mktemp()定義如下:

char *mktemp(char *template);
函數類似mkstemp()接受template參數, 其中template是一系列X結尾,必須由隨機值填充來建立隨機的臨時文件名,如:

fd = mkstemp(「/tmp/tempfileXXXXXX」);

當使用這個函數的時候,你需要保證X的尾數至少為6位,有些系統支持超過6為以增加隨機文件名.

此函數好處:
1,臨時文件可以被獲得(文件沒有建立).
此函數約束之處:
1,在絕大多數操作系統中,因此隨機數只有進程ID產生文件可以簡單的被猜測,這可能導致產生競爭環境問題.
2,文件名是唯一的,mktemp()沒有驗證是否存在.
3,如果使用不正確,在文件名產生和文件由程序實際產生之時產生競爭環境問題.

當使用mktemp()時候需要在文件產生后打開文件,在打開文件的時候需要小心檢查:

open(filename, O_WRONLY | O_CREAT, 0644);

上面的調用就不安全,即使文件已經存在它也會建立文件.造成存在文件被覆蓋.

open(filename, O_WRONLY | O_CREAT | O_EXCL, 0644);
上面的操作就可以安全的建立文件,因為如果文件存在調用操作將不成功.

2.3.4 一些額外的解決方法:
在建立臨時文件的時候在/tmp建立一個額外的目錄.如你使用mktemp()的時候產生一個臨時文件名,可是你可以使用這個作為目錄名,然後在其目錄內建立臨時文件.但是你需要確保建立的目錄許可權.通過umask()來設置一個安全的值。


2.4 文件競爭條件問題(Race Conditions)

在很多不同條件下會產生競爭條件問題,給予攻擊者攻擊的機會,這裡是一個普通的模式可以更安全的操作:
1,利用一個文件名的時候進行屬性檢查或者狀態檢查.
2,當文件操作時,同樣對相同文件名進行操作.

這種類型的攻擊通常利用符號連接來實施,讓我們看看下面存在於不安全的setuid root程序中的一段代碼:

int unsafeopen(char *filename)
{
struct stat st;
int fd;

/* obtain the files status information */

if (stat(filename, &st) != 0)
return -1;

/* make sure that the file is owned by root ? uid 0 */

if (st.st_uid != 0)
return -1;

fd = open(filename, O_RDWR, 0);
if (fd < 0)
return -1;

return fd;
}

上面的函數進行了如下操作:

1,檢查是否文件名存在並檢查是否屬主為ROOT(UID 0)
2,打開文件.

由於他們是兩個獨立的系統調用,造成在2個特定的操作存在一個時間的延遲,在這個延遲段中,就可能造成文件和系統特性被改變,攻擊者可以用下面的方法利用這個問題:

1,他可以建立一個符號連接把/tmp/filename指向一屬於ROOT的文件,如/etc/passwd
2,stat()調用會遵循符號連接,並返回信息給屬性為root用戶(UID=0)的/etc/passwd.
3,攻擊者去掉符號連接並把它指向一個屬主為他自己的文件.
4,程序很自然的打開/tmp/filename(此文件這時已經指向攻擊者的文件)來讀操作,這個讀取的數據本來應該是另一屬於ROOT進程的文件數據.

這個函數的安全版本可如下所示:

int safeopen(char *filename)
{
struct stat st, st2;
int fd;

/*獲得文件狀態信息 */

if (lstat(filename, &st) != 0)
return -1;

/* 確保文件是一個常規文件 */

if (!S_ISREG(st.st_mode))
return -1;

/* 確保這個文件屬主是root ? uid 0 */

if (st.st_uid != 0)
return -1;

/* 打開文件 */

fd = open(filename, O_RDWR, 0);
if (fd < 0)
return -1;

/* 現在我們fstat() 文件,確保它還是同一文件! */

if (fstat(fd, &st2) != 0) {
close(fd);
return -1;
}

/*這裡確保inode和device號碼等於我們實際打開的文件,
*比較我們執行的文件是開始lstat()調用的文件.
*/

if (st.st_ino != st2.st_ino || st.st_dev != st2.st_dev) {
close(fd);
return -1;
}

return fd;
}
上面函數中使用lstat()代替stat(),假如指定文件名發生符號倆界,它返回連接的狀態.然後打開文件,或者打開文件描述符的狀態,比較inode 和device號狀態信息,如果發現它們不是同樣的,函數將異常中斷.

2.5 chroot環境實現
當實現的網路服務對外提供介面時,就存在著一定危險.在開放者對所有代碼進行檢查確保他們代碼正確或者沒有明顯漏洞同時,外界因素如有問題的操作系統庫調用也會把未知的漏洞帶給這個服務.而UNIX系統就可以限制這種因素的存在,這可以通過所謂的chroot系統調用完成.

Chroot調用定義如下:

int chroot(const char *path);

調用chroot的程序可以改變當前文件系統的訪問範圍,注意這僅僅是限制了文件系統訪問,進程仍然可以被操作系統其他部分訪問(如系統和網路調用),當使用這個調用時,所提供的path(路徑)參數將被指定為文件系統新的ROOT目錄,這樣進程就不能再訪問在這個新ROOT目錄以為的文件或者目錄.只有超級用戶可以執行chroot系統調用.

一旦你使用了chroot系統調用,你需要變換為新的ROOT目錄,使用方法如下:

if (chroot(「/jail」) < 0 || chdir("/") < 0)
perror(「Failure setting new root directory」);

在許多狀況下,一旦你使用了chroot調用,它會丟棄超級用戶權利.這種運作方式可以限制如果你的程序存在問題也可以有一定的限制.

如果一用戶在chroot環境中是超級用戶的話,他就可能通過訪問一些系統的關鍵功能而突破chroot環境:
1,如果可以通過mknod()來創建一些重要的操作系統設備,如raw disk和內核內存設備,這樣一旦建立,就有可能直接訪問磁碟和內存,導致突破chroot環境.
2,如果有能力依附在么有運行在chroot環境中的其他系統進程和影響他們的操作(如插入惡意代碼在裡面,如有過的突破FTP環境).就可能突破受限環境.在絕大多數操作系統中ptace()系統介面存在這個功能,此函數設置用來允許對系統進程進行調試(當然也可以用在不正當用途).
3,有能力發送信號給其他進程和影響它們的操作,這即使是沒有超級權利的用戶也可能發生,如果用戶的權利等同與想要發生給信號的進程.

有些操作系統通過一種叫securelevel的安全機制提供對上面這些情況的保護(如FREEBSD),這種機制可以讓操作系統運行在更高的安全級別上.如不能訪問/dev等.

對於程序來說在執行chroot后丟棄權利和以」nobody」用戶來運行是比較不錯的操作,不過你需要注意即使你以」nobody」用戶來運行,當前進程還可能影響其他進程的操作,這裡建議建立一個特殊的用戶給要運行的進程,而這個進程沒有用在其他任何目的,沒有其他任何操作.

確保chroot環境沒有任何setuid或者setgid來使進入了chroot的攻擊者提高權利.

2.5 丟棄權利(Dropping Privileges)
大多數程序需要權利只能通過超級用戶或者其他特殊帳戶獲得訪問系統資源的權利,在網路服務中,經常需要分配有一定權利的TCP或者UDP口,而這個操作不是一般用戶可以綁定的,象BIND程序需要ROOT權利綁定53埠來服務域名查詢.

而這個特殊的權利一般只在初始化的時候的時候需要,而許多大的程序,包括幾十萬行複雜的代碼,也存在從頭到尾沒有丟棄特定的權利(這經常發生在一些商業化程序中).

有時程序往往提供了這個特殊權利后就沒有深入考慮,而造成不少可利用漏洞,當詢問開發者為何不丟棄這危險的權利時,他們回答往往是為了一些簡單的文件訪問或者訪問一些受限系統而已,不過他們沒有想到用其他方法來解決.

任何需要特殊權利的程序設計用來在初始化時分配所有資源,然後在丟棄這些權利是非常重要的,在OPENBSD中很多這樣的程序為了這個目的而重新被設計過.

要丟棄這些特殊權利,進程只需要設置它們相對應低權利的有效用戶ID和組ID即可.這可以通過使用seteuid 和setegid系統調用而臨時完成,而通過setuid() 和setgid()程序永久性的丟棄權利(更多信息將會在下面的setuid程序部分解釋).

警告:當丟棄特殊權利時,確保你先改變進程的組ID,如果你先設置了用戶ID,程序就不再以超級用戶運行,因此它就沒有權利在更改組ID,這意味著組ID權利沒有被丟棄,如果程序有漏洞的話任何人還可以通過組ID獲得超級用戶的權利.

記住:檢查setuid和setgid調用的返回值是很重要的,當你丟棄特殊權利和調用失敗的話,特殊權利也是不會被成功丟棄,因此這時返回值沒有很好檢查的話,程序依舊是以超級用戶的權利在運行.

2.6.1 在網路服務中需要注意的問題

在網路服務中要丟棄特殊權利,你需要在丟棄權利的時候選擇一用戶來運行程序,在許多請情況下,一般選擇」nobody」用戶,因為這個用戶往往對操作系統訪問的權利最小:

int drop()
{ struct passwd *pep = getpwnam("nobody");
if (!pep)
return ?1;

if (setgid(pep->pw_gid) < 0)
return ?1;
if (setuid(pep->pw_uid) < 0)
return ?1;
return 0;
}

2.6.2 在本地setuid程序中需要注意的問題

當setuid 和setgid程序執行的時候,進程的實際用戶ID(UID)和實際組(GID)和執行用戶UID的UID,GID是相等的,但是有效ID(EUID),有效組ID(EGID),保留用戶ID和保留組ID在執行的時候還是以文件屬主相同.你可以根據你的目的來永久或臨時丟棄權利.

要永久性的丟棄權利,程序需要設置euid,egid,saved uid 和saved gid為實際的UID和GID,
setuid()和setgid()調用會設置實際ID,有效ID和保留ID為同一特定值,由於實際UID和GID是那些沒有特殊權利的用戶,下面的例子將會設置所有3個ID為當前實際ID:

if (setgid(getgid()) < 0)
return ?1;
if (setuid(getuid)) < 0)
return ?1;

要臨時丟棄特殊權利,而在後續的操作中還要繼續獲得這些權利,你可以設置有效ID為所需值.因為有效ID是用來執行系統調用和許可權檢查的,而保留ID還是同樣的ID,允許你在後續操作重新獲得特殊權利.不過你要確保保存ID的值以便後續操作使用:

struct passwd *pep = getpwnam("nobody");
uid_t saved_uid;
gid_t saved_gid;

if (!pep)
return ?1;

saved_uid = geteuid();
saved_gid = getegid();
if (setegid(pep->pw_gid) < 0)
return ?1;
if (seteuid(pep->pw_uid) < 0)
return ?1;
if (setegid(saved_gid) < 0)
return ?1;
if (seteuid(saved_uid) < 0)
return ?1;

2.7 產生隨機數

隨機數發生器沒有一個很容易的解決方案,大多數系統提供一個pseudo-radom數字發生器庫調用,但需要知道這個」pseudo」是」假的,冒充的」的意思.有些操作系統提供內置隨機數字發生器,一般通過設備驅動程序提供隨機數據—一般通過內核驅動程序混合和雜亂系統上的各種事件和變數.我們這裡將講述各種操作系統獲取隨機數據的方法.

2.7.1 Linux
當前Linux系統提供/dev/random和/dev/urandom設備,這些設備是通過基於各種系統狀態,收集並雜亂它們而產生隨機數.

/dev/random和/dev/urandom對於產生加密KEY,和其他應用程序所需的隨機數已經足夠安全了,一般來說不可能猜測下一個隨機號碼.

兩個不同之出是/dev/random會用完隨機位元組而後的」讀取者」必須等待一段時候才可用,這樣如果系統上沒有足夠的活動來產生額外的隨機數據,就會導致需要等待比較長時間來獲得新數據.

2.7.2 OpenBSD

OpenBSD內核使用滑鼠間斷時候,網路數據間隔反應時間,擊鍵間隔時間和磁碟IO信息來填充信息量池,隨機數由內核收集並通過設備傳遞給用戶空間程序,而這個設備就是/dev/random.

下面的信息來自OPENBSD手冊:

各種隨機設備數據產生的數據取決於不同隨機量,信息量數據可從系統活動中獲得,並通過各種hash或信息摘要函數產生輸出.
/dev/random 保留設備為以後支持硬體隨機發生器.
/dev/srandom強壯的隨機數據,這個設備返回可依賴的隨機數據,如果數據的信息量不充足的時候,驅動程序會暫停直到更多數據被採集,平均信息量池數據使用MD5轉換為輸出數據.
/dev/urandom 和上面一樣,但它不保證數據的強度, 平均信息量池數據使用MD5轉換為輸出數據.當數據不充足時,驅動程序會繼續輸出數據。
/dev/prandom 類似pseudo-random發生器.
/dev/arandom 作為需要,數據由ARC4發生器發生,然後產生高質量pseudo-random輸出數據,arc4random(3)函數從這個設備在用戶空間庫提供第二級別的ARC4 hashed數據。

2.8調用子進程

一般情況下一個程序需要執行另一個程序,當在一個擁有特殊權利的程序(如setuid或網路服務中)需要這樣的操作時,你務必小心應付.
1,永遠不要使用下面的庫調用:
system()
popen()
上面兩個函數通過使用UNIX系統的SHELL解釋程序/bin/sh來執行特定的程序,而這兩個程序牽涉到大範圍的安全問題,你必須使用execl 或者execv調用代替.

2,確保所有文件描述符關閉,以防止子進程繼承父進程權利來訪問重要文件.

3,確保指定程序的全部路徑,防止偽造程序的木馬被執行.

4,在執行之前丟棄特殊權利,而使子進程不能繼承父進程權利.

5,確保傳遞給子進程的環境變數是凈化過的,並且只傳遞所需要的環境變數,因為環境變數可以被多次定義,造成安全問題的發生.

2.9 資源限制

2.9.1 core文件
core文件在UNIX程序執行異常時產生,這些異常包括內存或者堆棧被破壞,或者非法內存或不合理結構被訪問.當這些異常發生時,操作系統把當前執行程序的內存數據寫到一磁碟文件中,通常叫』core(核心傾倒文件)』或』program.core』,一般這個』core』文件是用來分析程序信息和幫助判斷程序為何出錯.此文件的建立往往造成2個安全問題:

1,由於程序所有內存內容被寫到文件中,有可能保存重要的,敏感的信息,如密碼.

2,過去一些操作系統在建立這些core文件時沒有檢查此文件是否在系統中存在,這樣可以導致攻擊者建立符號連接,如果執行的程序是setuid/setgid root程序,就可以覆蓋系統上重要的文件,造成拒絕服務攻擊,或者其他更嚴重安全問題.

一個很好的例子是以前Solaris操作系統中的FTP服務程序,攻擊者可以連接到FTP服務程序,在執行其他命令前提送PASV命令,造成FTP伺服器崩潰.在這個崩潰中,FTP伺服器會傾倒出內存內容到一個文件,並存放在系統ROOT目錄,通過分析這個文件,攻擊者可以獲得加密過的HASH欄位,通過暴力破解可以獲得用戶名和密碼.

如果你的程序在存在中會包含一些安全信息,明智的做法是不允許異常情況下建立core文件.你可以使用setrlimit()函數調用來完成它:

int setrlimit(int resource, const struct rlimit *rlp);

通過設置resource類型為RLIMIT_CORE,你可以設置被建立core文件大小,如果你定義大小為0,就防止了core文件被建立.

int nocore()
{
struct rlimit rlp;

rlp->rlim_cur = 0;
rlp->rlim_max = 0;

return(setrlimit(RLIMIT_CORE, &rlp));
}

3.0 WINDOWS操作系統
此部分討論的安全編程技術牽涉到WINDOWS操作系統,我們集中討論WINDOWS NT系統,不過很多內容同樣適用與其他版本WINDOWS系統,如WIN95和WIN98,也適合於WINDOW 2000系統.

3.1 訪問控制列表 (Access Control Lists)
WINDOWS NT的安全多數通過基於訪問控制列表(ACL),ACL可以在許多不同類型對象上指派,如:

1,NTFS文件系統上的本地或者遠程文件和目錄.
2,有名管道.
3,本地或者遠程印表機.
4,本地或者遠程WIN32服務.
5,網路共享.
6,註冊表鍵值.
7,信號,事件, 互斥通信(mutex)和時間.
8,進程,線程,文件映射對象.
9,WINDWO工作站和桌面
10,目錄服務對象

ACL定義對象的屬主是誰,誰可以訪問對象,在多數情況下,當建立一新對象時,ACL指派給對象的權利不一定安全.一般有4種不同類型的安全信息應用在對象上,當對一對象設置或者獲取安全信息時,你必須非常熟悉它們:

OWNER_SECURITY_INFORMATION

這選項指定對象的屬主,在多數情況下,默認設置為建立此對象用戶.

GROUP_SECURITY_INFORMATION

這選項說明了應用於對象上的組許可權,定義那些組可以訪問對象.

DACL_SECURITY_INFORMATION

此選項設置」任意」訪問控制列表在指定的註冊表鍵上.

SACL_SECURITY_INFORMATION

此選項設置」系統」訪問控制列表在指定註冊表鍵上.

WINDOWS NT提供SetSecurityInfo()函數用來指派安全描述符給上面任意一個對象,這函數描述如下:

DWORD SetSecurityInfo(
HANDLE handle, // 對象句柄
SE_OBJECT_TYPE ObjectType, // 對象類型
SECURITY_INFORMATION SecurityInfo, // 要設置的安全信息類型
PSID psidOwner, // 新屬主SID指針
PSID psidGroup, // 新組SID指針
PACL pDacl, //新DACL指針
PACL pSacl // 新 SACL指針
);

SetSecurityInfo()之設置ACL(屬主,組,DACL,SACL)的其中之一,而不是全部,你必須在SecurityInfo變數中指定要設置的ACL,你也必須在ObjectType里指定對象類型的句柄(註冊表鍵值,文件等).

另外這個函數有多個特殊對象函數可以用來指派安全描述符給各自的對象類型,註冊表,有自己的設置函數用來對註冊表鍵值設置ACL,本質上用途和SetSecurityInfo()函數相同.

其中一個最大問題就是當多數對象建立時候,它們的ACL往往允許」Everyone」來訪問它們的對象,這是應用程序開發者必須特別小心的.

3.2 註冊表鍵值

WINDOW使用註冊表存儲重要的配置信息來操作你的程序,而多數許多重要的安全信息保存在註冊表中.更要命的是,對註冊表鍵的保護往往不夠充分而導致攻擊者可以讀或修改註冊表值.不過存在一些特殊函數可以管理註冊鍵的ACL. WINDOWS提供下面2個函數來支持查看和設置註冊表鍵值許可權.

LONG RegGetKeySecurity(
HKEY hKey, // 要設置的鍵句柄
SECURITY_INFORMATION SecurityInformation, // 描述符內容
PSECURITY_DESCRIPTOR pSecurityDescriptor, //鍵描述符地址
LPDWORD lpcbSecurityDescriptor //描述符緩衝大小
);

LONG RegSetKeySecurity(
HKEY hKey, // 要設置的鍵句柄
SECURITY_INFORMATION SecurityInformation, // 描述符內容
PSECURITY_DESCRIPTOR pSecurityDescriptor // 鍵描述符地址
);

3.3 文件
確保使用ACL充分保護你程序的文件,下面是兩個重要的應用情況:

1,應用程序需使用於所有用戶
n 由於所有用戶要有權利訪問它們,所有不能限制應用程序和目錄的訪問.
n 限制所有應用程序產生的文件,因為它們是每個用戶獨立產生的,確保只有產生文件的用戶可訪問.
2,應用程序只用於管理員
--- 限制所有其他用戶不能訪問應用程序和它的目錄,可以在安裝過程中指派.
n 限制所有應用程序產生的文件,因為它們是每個用戶獨立產生的,確保只有產生文件的用戶可訪問.

下面是這兩個函數的描述:

BOOL GetFileSecurity(
LPCTSTR lpFileName, //文件名字元串地址
SECURITY_INFORMATION RequestedInformation, // 請求信息 PSECURITY_DESCRIPTOR pSecurityDescriptor, // 安全描述符地址
DWORD nLength, // 安全描述符緩衝地址
LPDWORD lpnLengthNeeded // 緩衝地址大小
);

BOOL SetFileSecurity(
LPCTSTR lpFileName, // 文件名字元串地址 SECURITY_INFORMATION SecurityInformation, // 要設置的信息類型
PSECURITY_DESCRIPTOR pSecurityDescriptor // 安全描述符地址);
};

3.4 產生隨機數
WINDOWS NT 提供CryptoAPI庫用來獲得隨機數, CryptoAPI提供CryptGenRandom()函數可以指定所需隨機數所需位元組數:
BOOL WINAPI CryptGenRandom(
HCRYPTPROV hProv,
DWORD dwLen,
BYTE pbBuffer,
);
下面是Mcirosoft MSDN(Copyright 2000)的引用:

「通過cryptographically random這個功能產生數據,比一般隨機數發生器如C編譯器中自帶的函數更有隨機性.

這個函數用來產生隨機初始化向量和salt值.

所有軟體隨機發生器工作方式基本相同,它們採用 叫seed的」種子」作為隨機數,然後使用演算法來產生pseudo-random位序列,其中處理困難的部分是獲得的seed是否真正隨機.這基於用戶輸入反應時間或者一個或者多個硬體成分.

如果應用車工女婿要訪問一個好的隨機資源,它可以在調用CryptGenRandom使用隨機數據填充pbBuffer緩衝,CSP然後使用這個數據更進一步隨機化它的內在seed.

4.0 設計協議
網路協議定義了網路設備之間的通信格式,當設計網路協議的時,有許多因此牽涉到安全問題,下面是考慮設計網路協議時需要注意的問題:

1,攻擊者可能被動性的監視網路,檢查網路上的所有數據.
2,攻擊者可能截取通信,修改數據,再重發修改的通信數據.
3,攻擊者可能從另一主機上偽造信息包.
4,攻擊者可能發送不遵循協議規格的信息包.

上面幾條是設置網路協議需要注意的問題,通過對數據加密,認證和合法數據的組合,多數問題可以被避免.

4.1 認證
認證在網路協議中有輕重之分,如下:
1,通過登陸帳戶認證
這種認證通常在會話開始階段產生,如POP, IMAP, NNTP, telnet, ftp 服務都是通 過連接后對輸入的用戶名和密碼進行檢查操作.多數情況下,用戶名和密碼信息在網路上是通過明文發送,往往可以被嗅探到.

2,加密認證
這種認證方法通常利用公開KEY加密法來認證用戶或者客戶,如果雙方都被配置有公共KEY也可以用secret-key演算法.

4.2機密性
如果你的協議設計用來攜帶敏感信息那保護協議數據被監視就尤為重要,SSL協議就是在WWW上用來加密電子商務事務的安全協議.

5.0 工具
手工來查看數千萬行的代碼是枯燥而又缺乏安全的工作,現在有不少工具可以用來幫助發現WINDOWS和UNIX下源代碼的安全問題.它們通常通過分析能提供數據來指出有潛在問題的調用.

除了幫助發現源代碼安全問題的工具,現在也有不少工具可以防止有漏洞程序的執行,這些工具有不少種類,包括提供在編譯時增加內容防止緩衝溢出,和在實時庫調用時截獲危險的庫調用.

5.1 UNIX工具

ITS4

http://www.rstcorp.com/its4
「Software Security Group設計了設計,分析和測試有安全問題的軟體,我們開發的ITS4自動幫助檢查源代碼問題,ITS4是一個靜態掃描C和C++源代碼中存在的安全漏洞的簡單工具,它以命令行方式工作在UNIX環境中,如果你在WINDOWS下安裝了CygWin,也可以在工作在WINDOWS下.

StackGuard from Immunix

http://www.immunix.org

「StackGuard是編譯處理方式來防護那些存在堆棧破壞的程序和系統攻擊,堆棧破壞是最普通的安全漏洞攻擊方式,使用StackGuard環境下編譯的程序可以不需要源代碼改變的情況下對堆棧破壞攻擊方式免疫.當一個程序中的漏洞被利用時, StackGuard可以檢測到進程中的攻擊,阻止程序的執行.

Libsafe from Bell Labs

http://www.bell-labs.com/org/11356/libsafe.html
「緩衝溢出攻擊在近年是一種很流行的攻擊方法,我們提供新的方法來探測和處理這些攻擊.比較以前的方法,我們的方法不需要操作系統的任何修改並對所有已經存在的兩進位程序適用.我們的方法不需要訪問有缺陷程序的源代碼,不需要重新編譯或者停止程序進程.而且它可以在系統廣泛的透明的實現.我們的解決方法基於在軟體層中部截獲有可能存在漏洞的庫函數的系統調用.使用相應函數的替換版本來實現原來的函數功能,在某種意義上來說,我們假定在當前棧幀中包含著緩衝溢出,而通過安全函數的替代,阻止了對堆棧中返回地址的破壞.

5.2 WINDOW工具
下面所列的工具使用在WINDOWS系統下保護軟體.需要注意的是這些工具的專業性非常難於確定,所以你在使用它們的時候必須足夠小心.
BOWall

http://developer.nizhny.ru/bo/eng/BOWall
「BOWall實現對WINNT4.0下的程序提供緩衝溢出攻擊包括,其中提供了兩種方法:

1,監視有漏洞的函數,有潛在漏洞的DLL將被更新,並替代如strcpy, wstrcpy, strncpy, wstrncpy, strcat, wcscat, strncat, wstrncat, memcpy, memmove, sprintf, swprintf, scanf, wscanf., gets, getws, fgets, fgetws函數,更新內容還包括代碼完整性檢查,如檢查棧基指針本地變數.
2,防止動態庫函數在數據段和堆棧段中執行,這種方法通過額外代碼對函數調用中的地址進行檢查,如果調用屬於數據或者堆棧段,程序將被停止執行.

6.0 參考和資源

[1] Smashing the Stack for Fun and Profit By Aleph One

http://www.securityfocus.com/data/library/P49-14.txt
[2] Fuzz Revisited: A Re-examination of the Reliablity of UNIX Utilities and Services

ftp://grilled.cs.wisc.edu/technical_papers/fuzz-revisited.ps.Z
[3] The Unix Secure Programming FAQ, Tips on security design principles, programming methods, and testing By Peter Galvin

http://www.sunworld.com/sunworldonline/swol-08-1998/swol-08-security.html
[4] Communications with Theo de Raadt (deraadt@openbsd.org)

[5] Thomas Ptacek Bugtraq message regarding FreeBSD C runtime library overflow


感謝http://www.securityfocus.com,此文章原文來自securityfocus的郵件列表.






















[火星人 ] 安全編程詳解v1.00已經有525次圍觀

http://coctec.com/docs/program/show-post-72247.html