1. 第三章 安全
Java在一開始的時候,側重於互聯網的應用,即applet,applet是一段JAVA程序,這個程序運行在瀏覽器中,它來源於網絡。爲了避免這些來源於其它地方的代碼不對本地機器造成傷害,JAVA提供了基本沙箱來運行這些程序。沙箱保證了applet不能執行下面的操作:
讀寫硬盤
開啓到宿主機的socket鏈接
創建新的進程
裝載新的動態鏈接庫
1.1 1.0版本的基本沙箱
組成沙箱的基本組件:
l 類裝載器結構
l Class文件檢驗器
l 內置於JAVA虛擬機(及語言)的安全特性
l 安全管理器及JAVA API
1.2 類裝載器體系結構
類裝載器在三個方面保證JAVA代碼的安全
l 一是防止惡意代碼去幹涉善意代碼
n 不同的類裝載器裝入的類都具有不同的命名空間
n 同一個類裝載器中不能多次裝入同一個類
n 同一個類可以被不同的類裝載器裝入(那麼它們將具有不同的命名空間)
n 在不同命名空間中的類互相不能訪問
l 二是它守護了被信任的類庫邊界
n 這主要通過不同的類加載器來加載可靠的和不可靠的代碼來實現
n 雙親委派模式:一個類裝載器轉載一個類的時候,首先委託雙親來裝載,如果雙親無法裝載,則委託雙親的雙親來裝載,直到啓動類裝載器;如果這些裝載器都無法裝載,則由這個類裝載器自己來裝載
n 比如我們運行一個自己寫的JAVA程序,那麼在程序啓動之前,JAVA虛擬機將創建三個類裝載器:啓動類裝載器和擴展類裝載器和系統類裝載器,啓動類裝載器負責裝載JDK核心類庫中的類,擴展類裝載器負責裝載擴展目錄中的類,而系統裝載器負責裝載類路徑中的類。
加載的時候,一路往上委託,從啓動類加載器嘗試加載,如果不行,再由擴展類裝載器來裝載,如果還不行,則由系統(類路徑)類裝載器來裝載,如果還不行,則由網絡類裝載器來裝載。
這種委派模式防止了其它類僞裝標準JDK類庫中的類。比如,網絡類裝載器如果試圖裝載一個java.lang.Integer類,因爲這個類是JDK核心類庫中的類,所以它將由啓動類裝載器裝載,避免了僞裝的發生。
還有一種情況,比如假設我們現在編寫一個java.lang.Virus(病毒)類,僞裝是java.lang這個包的一個類,如果僞裝成功,它將得到java.lang這個包的包訪問權限。現在這個類,確實是由網絡類加載器來加載了,但是它將得不到核心類庫中java.lang這個包的包訪問權限,因爲這個類和其它在java.lang這個包中的類不屬於同一個類裝載器。也就是說在編譯階段,雖然java.lang.Virus和java.lang.Integer等是屬於同一個包的,但是在運行的時候,它們是由不同的類裝載器的,這稱爲運行時包,即它們不屬於同一個運行時包。對於這樣的類,它將得不到任何特殊的訪問權限。
剛纔講到不同類裝載器裝載的類不能互相訪問,那麼具有父子關係的類裝載器呢?這些應該是可以互相訪問的?
答:子女裝載器可以看到父母裝載器裝載的類,但父母裝載器裝載的類將看不到子女裝載器裝載的類。
l 三是它將代碼歸入某類(稱保護域),該類確定了代碼可以進行哪些操作
n 類裝載器將裝載的類放到一個保護域中,一個保護域定義了它將得到怎樣的權限
1.3 Class文件檢驗器
Class文件就是一個字節碼序列,在裝載的時候,需要對class文件進行檢驗,以便它符合規範(而不是一個由黑客創建的類)。Class文件檢驗器在執行前,對class進行校驗。分爲四趟獨立的掃描來完成檢驗:
l 第一趟:class文件的結構檢查
n 這個主要確保它是一個class文件,而不是別的文件(比如.exe/.txt/.jpg等文件)以及文件是否有刪節等等
l 第二趟:類型數據的語義檢查
n 將檢查比如一個方法描述符是否合法、除Object類以外的其它所有類是否都有一個超類、final類沒有被子類化、final方法沒有被覆蓋等等
n 實際上就是檢查這個類是否符合在編譯時必需遵守的強制規則
l 第三趟:字節碼驗證
n 首先理解字節碼流:操作碼指令+操作數,這樣的序列,就是字節碼流。
n 執行線程,就等於逐個執行操作碼指令
n 每個線程,被授予其自己的JAVA棧,這個棧由不同的棧幀(一個內存片段)構成
n 每一次方法調用,都會獲得一個自己的棧幀,棧幀用來存放局部變量和計算的中間結果(所謂中間結果,比如:int i = a + b + c + d,那麼首先執行a+b,其結果就是中間結果,把這個中間結果再加上c,得到另外一箇中間結果,然後再加上d,最後得到結果i)
n 在棧幀中,用於存儲方法的中間結果的部分稱爲該方法的操作數棧
n 在執行一個操作碼指令時,除了可以使用直接緊跟其後的操作數之外,還可以使用操作數棧中的數據,或局部變量中的數據
n 字節碼驗證就是驗證操作碼指令和操作數的合法性等等
l 第四趟:符號引用的驗證
n 在動態鏈接過程中,包含在一個類中的符號引用被解釋時,將進行第四趟驗證。
n 一個類可能包含了對其它類的符號引用,一個符號引用就是一個字符串,它給出了這個引用的相關信息。包括對類的引用,對字段的引用,對方法的引用。如果是對類的引用,需給出類的全名;如果是對字段的引用,需給出類名、字段名和字段描述符;如果是對方法的引用,需給出類名、方法名、以及方法的描述符
n 這一般在代碼運行到需要加載其它類的時候纔會去執行這第四趟驗證(即延遲驗證了)
n 所謂動態鏈接:即將符號引用解析爲直接引用的過程。
u 查找被引用的類(有必要的話就加載它)
u 將符號引用替換爲直接引用,例如一個指向類、字段或方法的指針
1.4 Java虛擬機中內置的安全特性
l 類型安全的類型轉換
l 結構化的內存訪問(無指針算法)
l 自動垃圾收集
l 數組邊界檢查
l 空引用檢查
1.5 安全管理器和JAVA API
前三個特性主要是避免JAVA虛擬機和應用程序受到外來代碼的破壞。安全管理器,則是爲了避免在虛擬機中運行的程序惡意訪問外部資源。
安全管理器就是用來控制對外部資源的訪問的。
在啓動java程序的時候,如果沒有設置安全管理器,則將不使用安全管理器。所以所有API都可以使用。
安全管理器負責兩個方面的工作:定義安全策略、執行安全策略
JAVA從1.2版本開始,已經提供了缺省的安全管理器實現,因此,我們只需要編寫一個策略文件即可(文本文件)。
1.6 代碼簽名和認證
java.security及其子包。
認證可以使得用戶確認,由某些團體擔保的.class代碼是值得信任的,並且這些class文件在到達虛擬機之前沒有被改動過。如果用戶信任這個團體,則可以減少沙箱對代碼所實施的限制!可以對由不同團體簽名的代碼建立不同的安全限制。
在繼續往下之前,你需要了解關於加解密的基礎知識:對稱加密算法、非對稱加密算法、不可逆加密算法。不可逆加密算法的目的是防篡改,即從一個文檔的數據流中生成一段散列值(64位或128位)。一般情況下,不同的文檔生成的散列值不同(相同的概率非常小),所以,可以認爲這段散列值就代表了這個文檔,一旦文檔被篡改,則散列值就不同了!所以,只要把散列值和文檔一起傳輸,接收方對接收到的文檔再次計算其散列值,判斷這個散列值和接收到的散列值是否一致,就可以判定這個文檔是否經過了篡改!
看起來想法不錯,但是這裏面存在的問題就是,散列值在傳輸過程中也可能被更改,所以,必需對其加密(只對散列值進行加密,速度比較快),採用非對稱加密技術來對散列值加密。
非對稱加密技術有兩個密鑰:公鑰和私鑰,如果使用私鑰加密,則必須使用公鑰解密;如果使用公鑰加密,則必須使用私鑰解密。如果要對一個jar包的散列值進行加密,一般就用私鑰加密,然後公佈公鑰,對方使用公鑰來解密。
隨之帶來的問題就是,如何把我的公鑰公佈給對方?因此引入第三方可信任的證書機構來提供證書服務。即我可以把公鑰提交給證書機構,由證書機構來對我的公鑰用它的私鑰進行加密,最終得到的就是證書!jar文件的接收者可以使用證書機構的公鑰來解密證書,從而得到真正的公鑰,再用這個公鑰去解密散列即可。
1.7 策略
定義策略文件,以便控制applet或JAVA應用程序對資源的訪問權限(比如文件、socket等),
策略文件示例:
|
1.8 保護域
當類裝載器將類裝入JAVA虛擬機時,將給每個類型指派一個保護域。每個類型只能屬於一個保護域(一個保護域對應策略文件中的一個或多個grant子句)。