第一部分:java的安全基礎——虛擬機和字節碼安全
概論:安全問題對很多數人來說都非常重要。從其歷史看,Java安全主要意味著虛擬機和字節碼安全。然而這個看法忽略了兩個重要方面—應用程序和網絡安全。在下面一系列文章中,Todd Sundsted講解了JAVA虛擬機安全,應用程序安全,網絡安全,解釋了應該采取什么樣的措施來全面鞏固你的Java安全。在這第一部分,他向我們解釋了Java安全的基礎:虛擬機和字節碼安全。
“似乎還沒有人曾因為寫出了不安全的Java代碼而遭解雇”。這句話是我對那句流行語“沒人曾因購買了IBM而遭解雇”的修正版本。那些更關心網絡速度和那些更有愛好為簡歷添加更多有價值項目的雇員經常犯下安全問題。
再來看看另一個令人擔心的現象:在我同治理人員和工程技術人員談論安全問題時,我經常發現他們對自己的行為存在一些誤解,他們認為不必考慮安全問題,因為“Java本身就等于安全”。在這樣錯誤觀念的指引下,工程師們沒有去考慮以下三個方面的安全問題:虛擬機安全,應用程序安全,網絡安全。
在以下一系列文章中,我會盡力修正這種錯誤見解。接下來,我將就三方面的問題來對Java安全進行討論,并舉列說明一般安全問題是怎樣竊入的。另外我也會介紹一些辦法來創建安全的應用程序。
·三種安全問題:
在Java初次露面時,開發者,研究人員,新聞媒體界對其安全問題就反響劇烈。在早前的時候,Java安全就是意味著字節碼安全和虛擬機安全。由于Java過去主要是作為下載到本地執行的小應用程序開發語言,下載下來的代碼的安全性和執行環境就是異常重要的事情。這種情況下的安全意味著正確安裝類裝載器和安全治理器以及驗證下載的代碼。
在我以前開發C/C++程序的數年里,我從沒擔心過虛擬機安全問題—這個問題完全是隨著Java而成了人們關注的中心。談到安全問題,我擔心的總是應用程序漏洞或是危及程序或系統安全的執行情況。在C++領域,應用程序上的安全包括限制“setuid”代碼范圍(在Unix環境,setuid代碼是作為另外的用戶進程來運行—典型的情況是超級用戶)并力圖避免緩沖溢出及其它類型的堆棧問題。
而分布式應用程序的引入則帶來了另外一些方面的問題。正如其名字所示,分布式程序由多個部分組成,每個部分都駐留在它自己的機器上,并通過公共網絡和其它部分通信。一個Web應用就是典型的列子。在網絡意義上的安全則意味著簽名,授權,應用程序組件,加密通信管道等。
許多開發人員并不清楚以上幾方面的不同,并以為Java在虛擬機一層安全了,那么整個應用程序就安全了。我很希望改變這種觀念。下面就開始來討論Java的虛擬機安全。
·安全基礎:虛擬機安全
虛擬機安全,長期以來一直是開發人員注重的焦點,幾乎直到現在也還是沒有結果。
我最初對討論虛擬機安全感到有愛好是在轉向應用程序和網絡安全之前。我決定給予它同另兩個部分同樣公平的時間來討論,這出于兩個理由:首先,優秀的編程教材因該包含過去6年來發現過的大量漏洞,第二,很多安全問題跨越了我要討論的三個方面。為了能透徹理解,你必須要全面熟悉三個方面,包括Java虛擬機安全。
假如你檢查過去6年發現的各種安全問題(看http://www.javaworld.com/javaworld/jw-06-2001/jw-0615-howto.Html#resources的官方清單),你將發現它們被分成一系列目錄。就所關注的虛擬機安全來講,最重要的兩種安全漏洞都是圍繞著未被驗證和可能非法的字節碼以及Java類型系統破壞來展開。在實際開發中,這兩者經常是關聯在一起
·未驗證代碼探秘
在JVM通過網絡從服務器上下載類代碼時,它并沒有辦法知道這些字節碼是否能安全執行。安全的字節碼永不會指示虛擬機執行讓Java運行時處于不懈調和無效的狀態。
通常,Java編譯器可以確保創建的類文件里的字節碼是安全的。然而也可以手工寫出Java編譯器不答應的字節碼。Java校驗器以一系列極富想像力的方法檢查所有這樣的字節碼并驗證那些不合規范的代碼。一旦校驗完成,JVM便知道程序代碼是安全的—只要校驗器正常工作。
下面讓我們來看看一個列子,以更好的理解校驗器所扮演的角色,并看看一旦校驗器失效會產生什么后果。
考慮一下下面這個類:
public class Test1
{
public static void main(String [] arstring)
{
Float a = new Float(56.78);
Integer b = new Integer(1234);
System.out.PRintln(a.toString());
}
}
當你寫完它并運行,程序將向屏幕打印出字符串“56.78”。這是個在類里分配的一個浮點型變量。我們即將修改一處代碼,欺騙虛擬機在整型變量上激活toString()方法而不是浮點型變量(你可以從網址下載并修改源代http://www.javaworld.com/javaworld/jw-06-2001/jw-0615-howto.html#resources)。
再來看看這段經反編譯后的代碼的輸出:
Method void main(java.lang.String[])
0 new #3
3 dup
4 ldc2_w #13
7 invokespecial #8
10 astore_1
11 new #4
14 dup
15 sipush 1234
18 invokespecial #9
21 astore_2
22 getstatic #10
25 aload_1
26 invokevirtual #12
29 invokevirtual #11
32 return
上面的代碼包含了main()函數的反編譯輸出。在這個方法的地址偏移量25處,虛擬機載入于偏移0到10處創建的浮點型變量的一個引用。這就是我們要修改的地方。
下面就是經修改后的反編譯代碼:
Method void main(java.lang.String[])
0 new #3
3 dup
4 ldc2_w #13
7 invokespecial #8
10 astore_1
11 new #4
14 dup
15 sipush 1234
18 invokespecial #9
21 astore_2
22 getstatic #10
25 aload_2
26 invokevirtual #12
29 invokevirtual #11
32 return
這個類在偏移量25處的字節碼是完全相同的,載入一個整型變量的引用。
注重看看,修改后的代碼仍然是安全的,這非常重要,這意味著JVM仍然將執行代碼而不會崩潰或是將錯誤代碼隔離開。然而校驗器仍然能分辨出這些變化。在我的系統里,在我運行這片代碼時,出現錯誤:
Exception in thread "main" java.lang.VerifyError:
(class: Test1, method: main signature: ([Ljava/lang/String;)V)
Incompatible object argument for function call
假如你關掉校驗器或是你找到一處虛擬機漏洞并非常規地通過了校驗器的檢查,那非法代碼就要啟動了。執行下面的命令,我接收到值:1234—整型變量值。
java -noverify Test1
這個列子并無多大害處,但潛在的危害確是巨大的。以上這樣的技術假如同虛擬機漏洞聯系起來,造成未被檢查的代碼得以執行,那么這將造成嚴重的類型混亂。
·類型混亂
類型的概念對java編程語言來說是渾然一體的。每個值都同一種類型相關聯,JVM就是用值的類型來決定什么樣的操作可以作用在什么樣的值上。
程序的類型信息對于虛擬機安全是至關重要的。一個被惡意的,未經驗證的代碼啟動的類型混淆攻擊會試圖讓JVM相信偽裝成為一個類實列的內存塊確實是是另一個類的實列,以此進行攻擊。假如攻擊成功,程序就會以設計者意想不到的方式來操作類實列。這種攻擊稱為“類型混淆攻擊”,因為虛擬機已經鬧不清被修改的類的類型。
假如類經過了完全的驗證,那么“類型混淆攻擊”是不會發生的。在上面的第二個列表中,校驗器捕捉了這個企圖并拋出了異常。也就是說,只要校驗器沒有被關閉或是被繞過,那么安全就是能夠保障的。
幸運的是,我所擔心的Java字節碼校驗器最后的一個漏洞在1999年末被發現。基于這個事實,你可能會認為自己不會在陷入危險中,然而,這過于疏忽大意了。
雖然漏洞越來越少,但還是有充足的機會留給狡猾的代碼混入程序之中。記住,你可以手工關閉校驗器檢驗。在接下來的文章中中,我列舉出三種主要的java程序,以向大家示列在怎樣的環境下關掉校驗器。其中一個程序有一個重要的RMI(遠程方法調用)組件(如你以后將要學到的,RMI可以讓類通過網絡載入到程序中,并讓你的程序失控)。假如你能避免這種情況發生,就不要關掉校驗器驗證。
JVM安全是java安全體系中非常重要的一方面。這些未驗證代碼和類型混淆方面的討論將有助于你理解為什么。對于下載代碼和類型系統來說,沒有適當的校驗保證,安全計算將成為一句空話。
新聞熱點
疑難解答