本文是來自
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/docs/guide/extensions/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
平臺的自動垃圾回收來做這種清除,因為回收器可能不會清除這段內存,或者很久后才會回收。盡早清除信息使得來自虛擬機外部的堆檢查攻擊變得困難。