安全
摘要:本文是來自Sun官方網站的一篇關於如何編寫安全的Java代碼的指南,開發人員在編寫一般代碼時,可以參照本文的指南
• 靜態欄位
• 縮小範圍
• 公用方法和欄位
• 保護包
• equals方法
• 如果可能使對象不可改變
• 不要返回指向包含敏感性資料的內部數組的引用
• 不要直接儲存使用者提供的數組
• 序列化
• 原生函數
• 清除敏感資訊
靜態欄位
• 避免使用非final的公用靜態變數
應儘可能地避免使用非final公用靜態變數,因為無法判斷代碼有無許可權改變這些變數值。
• 一般地,應謹慎使用易變的靜態狀態,因為這可能導致設想中相互獨立的子系統之間發生不可預知的互動。
縮小範圍
作為一個慣例,儘可能縮小方法和欄位的範圍。檢查包存取權限的成員能否改成私人的,保護類型的成員可否改成包存取權限的或者私人的,等等。
公用方法/欄位
避免使用公開變數,而是使用訪問器方法訪問這些變數。用這種方式,如果需要,可能增加集中安全控制。
對於任何公用方法,如果它們能夠訪問或修改任何敏感內部狀態,務必使它們包含安全控制。
參考如下程式碼片段,該程式碼片段中不可信任代碼可能設定TimeZone的值:
private static TimeZone defaultZone = null;
public static synchronized void setDefault(TimeZone zone)
{
defaultZone = zone;
}
保護包
有時需要在全域防止包被不可信任代碼訪問,本節描述了一些防護技術:
• 防止包注入:如果不可信任代碼想要訪問類的包保護成員,可以嘗試在被攻擊的包內定義自己的新類用以擷取這些成員的訪問權。防止這類攻擊的方式有兩種:
1. 通過向java.security.properties檔案中加入如下文字防止包內被注入惡意類。
...
package.definition=Package#1 [,Package#2,...,Package#n]
...
這會導致當試圖在包內定義新類時類裝載器的defineClass方法會拋出異常,除非賦予代碼一下許可權:
...
RuntimePermission("defineClassInPackage."+package)
...
2. 另一種方式是通過將包內的類加入到封裝的Jar檔案裡。
(參看http://java.sun.com/j2se/sdk/1.2/do...ons/spec.html)
通過使用這種技巧,代碼無法獲得擴充包的許可權,因此也無須修改java.security.properties檔案。
• 防止包訪問:通過限制包訪問並僅賦予特定代碼存取權限防止不可信任代碼對包成員的訪問。通過向java.security.properties檔案中加入如下文字可以達到這一目的:
...
package.access=Package#1 [,Package#2,...,Package#n]
...
這會導致當試圖在包內定義新類時類裝載器的defineClass方法會拋出異常,除非賦予代碼一下許可權:
...
RuntimePermission("defineClassInPackage."+package)
...
如果可能使對象不可改變
如果可能,使對象不可改變。如果不可能,使得它們可以被複製並返回一個副本。如果返回的對象是數組、向量或雜湊表等,牢記這些對象不能被改變,調用者修改這些對象的內容可能導致安全性漏洞。此外,因為不用上鎖,不可改變效能夠提高並發性。參考Clear sensitive information瞭解該慣例的例外情況。
不要返回指向包含敏感性資料的內部數組的引用
該慣例僅僅是不可變慣例的變型,在這兒提出是因為常常在這裡犯錯。即使數組中包含不可變的對象(如字串),也要返回一個副本這樣調用者不能修改數組中的字串。不要傳回一個數組,而是數組的拷貝。
不要直接在使用者提供的數組裡儲存
該慣例僅僅是不可變慣例的另一個變型。使用對象數組的構造器和方法,比如說PubicKey數組,應當在將數組儲存到內部之前複製數組,而不是直接將數組引用賦給同樣類型的內部變數。缺少這個警惕,使用者對外部數組做得任何變動(在使用討論中的構造器建立對象後)可能意外地更改對象的內部狀態,即使該對象可能是無法改變的。
序列化
當對對象序列化時,直到它被還原序列化,它不在Java運行時環境的控制之下,因此也不在Java平台提供的安全控制範圍內。
在實現Serializable時務必將以下事宜牢記在心:
• transient
在包含系統資源的直接控制代碼和相對位址空間資訊的欄位前使用transient關鍵字。 如果資源,如檔案控制代碼,不被聲明為transient,該對象在序列化狀態下可能會被修改,從而使得被還原序列化後擷取對資源的不當訪問。
• 特定類的序列化/還原序列化方法
為了確保還原序列化對象不包含違反一些不變數集合的狀態,類應該定義自己的還原序列化方法並使用ObjectInputValidation介面驗證這些變數。
如果一個類定義了自己的序列化方法,它就不能向任何DataInput/DataOuput方法傳遞內部數組。所有的DataInput/DataOuput方法都能被重寫。注意預設序列化不會向DataInput/DataOuput位元組數組方法暴露私人位元組數組欄位。
如果Serializable類直接向DataOutput(write(byte [] b))方法傳遞了一個私人數組,那麼駭客可以建立ObjectOutputStream的子類並覆蓋write(byte [] b)方法,這樣他可以訪問並修改私人數組。下面樣本說明了這個問題。
你的類:
public class YourClass implements Serializable {
private byte [] internalArray;
....
private synchronized void writeObject(ObjectOutputStream stream) {
...
stream.write(internalArray);
...
}
}
駭客代碼
public class HackerObjectOutputStream extends ObjectOutputStream{
public void write (byte [] b) {
Modify b
}
}
...
YourClass yc = new YourClass();
...
HackerObjectOutputStream hoos = new HackerObjectOutputStream();
hoos.writeObject(yc);
• 位元組流加密
保護虛擬機器外的位元組流的另一方式是對序列化包產生的流進行加密。位元組流加密防止解碼或讀取被序列化的對象的私人狀態。如果決定加密,應該管理好密鑰,密鑰的存放地點以及將密鑰交付給還原序列化程式的方式等。
• 需要提防的其他事宜
如果不可信任代碼無法建立對象,務必確保不可信任代碼也不能還原序列化對象。切記對對象還原序列化是建立對象的另一途徑。
比如說,如果一個applet建立了一個frame,在該frame上建立了警告標籤。如果該frame被另一應用程式序列化並被一個applet還原序列化,務必使該frame出現時帶有同一個警告標籤。
原生方法
應從以下幾個方面檢查原生方法:
• 它們返回什麼
• 它們需要什麼參數
• 它們是否繞過了安全檢查
• 它們是否是公用的,私人的等
• 它們是否包含能繞過包邊界的方法調用,從而繞過包保護
清除敏感資訊
當儲存敏感資訊時,如機密,盡量儲存在如數組這樣的可變資料類型中,而不是儲存在字串這樣的不可變對象中,這樣使得敏感資訊可以儘早顯式地被清除。不要指望Java平台的自動記憶體回收來做這種清除,因為回收器可能不會清除這段記憶體,或者很久後才會回收。儘早清除資訊使得來自虛擬機器外部的堆檢查攻擊變得困難。