Java
如果要用Java來開發安全程序
坦白地說
第一步(在學習了Java之後)就是要閱讀兩本有關Java安全的教材
即Gong [
]和McGraw [
](後一本特別要看第
節)
還應該看一下Sun發布的安全代碼指南
有一組描述Java安全模型的幻燈片可以從 免費獲取
下面是基於Gong [
]
McGraw [
]和Sun的指南的若干關鍵要點
不要使用公共域或變量
把它們聲明為私有的並提供訪問函數以限制對它們的訪問
除非有很好的理由
把方法都設為私有的(如果確實沒這樣做
說清楚其理由)
這些非私有的方法必須保護自己
因為它們可能會接收到受污染的數據(除非已經用其它方式對它們進行了保護)
避免使用靜態域變量
這樣的變量附著在類(而非類的實例)上
而類可以被其它類所定位
其結果就是靜態域變量可以被其它類找到
因此很難保證它們的安全
永遠不要把可變對象返回給潛在有惡意的代碼(因為代碼可能會改變它)
注意
數組是可變的(即使數組的內容不可變)
所以不要返回一個含有敏感數據的內部數組的引用
永遠不要直接保存用戶給定的可變對象(包括對象的數組)
否則
用戶可以把對象交給安全代碼
讓安全代碼
檢查
對象
並在安全代碼試圖使用數據時改變數據
應該在內部存儲數組前復制它們
而且要小心(例如
警惕用戶編寫的復制例程)
不要依賴於初始化
有好幾種方法給未初始化的對象分配內存
除非有很好的理由
應該使每件事都是確定的
如果某個類或方法不是確定的
攻擊者就可以用某種危險而無法預知的方法來擴展它
注意
作為安全性的交換
這會帶來可擴展性的喪失
不要在安全性上依賴包的范圍
若干類
如java
lang
缺省是關閉的
而且某些Java虛擬機(JVM)會讓你關閉其它包
否則
Java類是沒有關閉的
因此
攻擊者可以向包中引入一個新類
並用此新類來訪問你以為保護了的信息
不要使用內部類
在內部類轉換為字節代碼時
內部類會轉換為可以訪問包中任意類的類
更糟的是
被封裝類的私有域靜悄悄地變成非私有的
允許內部類訪問!
最小化特權
如果可能
完全不要請求任何特殊的許可
McGraw更進一步地推薦不要標記任何代碼
我認為可以標記代碼(這樣用戶可以決定
只有列表上的發送者可以運行標記過的代碼
)
但在編寫程序時要使程序不需要沙箱設置之外的權限
如果一定要有更大的權限
審讀代碼就會特別困難
如果一定要標記代碼
應該把它們都放在一個檔案文件裡
這裡最好引用McGraw [
]的原文
此規則的目的是防止攻擊者使用混合匹配攻擊
構建新applet或庫把某些標記類與有惡意的類連接在一起
或者把根本意識不到會被一起使用的標記類連接在一起
通過把一組類標記在一起
就可以使這種攻擊更困難
現有的代碼標記系統在防止混合匹配攻擊上做得還不夠
所以這一規則還不能完全防止此類攻擊
但使用單個答案沒什麼壞處
應該使類不可被復制
Java的類復制機制允許攻擊者不運行構建函數就實例化某個類
要使類不可被復制
只要在每個類裡定義如下方法
public final void clone() throws java
lang
CloneNotSupportedException {
throw new java
lang
CloneNotSupportedException();
}
如果確實需要使類可被復制
那麼可以采用幾個保護措施來防止攻擊者重新定義復制方法
如果是定義自己的復制方法
只需要使它是確定的
如果不是定義自己的復制方法
至少可以通過增加如下內容來防止復制方法被惡意地重載
public final void clone() throws java
lang
CloneNotSupportedException {
super
clone();
}
應該使類不可序列化
系列化運行攻擊者看到對象的內部狀態
甚至私有部分
要防止這一點
需要在類裡增加如下方法
private final void writeObject(ObjectOutputStream out)
throws java
io
IOException {
throw new java
io
IOException(
Object cannot be serialized
);
}
甚至在序列化沒問題的情況下
也應該對包含直接處理系統資源的域和包含與地址空間有關信息的域使用臨時關鍵字
否則
解除類的序列化就會允許不適當的訪問
可能還需要把敏感信息標識為臨時的
如果對類定義了自己的序列化方法
就不應該把內部數組傳遞給需要數組的DataInput/DataOuput方法
其理由在於
所有的DataInput/DataOuput方法都可以被重載
如果某個可序列化的類向某個DataOutput(write(byte [] b))方法直接傳遞了一個私有數組
那麼攻擊者就可以構建子類ObjectOutputStream並重載write(byte [] b)方法
從而可以訪問並修改那個私有數組
注意
缺省的序列化並沒有把私有字節數組域暴露給DataInput/DataOutput字節數組方法
應該使類不可被解除序列化
即使類不可被序列化
它依然可以被解除序列化
攻擊者可以構建一個字節序列
使它碰巧是被解除序列化的某個類實例
而且具有攻擊者選定的值
換句化話說
解除序列化是一種公共的構建函數
允許攻擊者選擇對象的狀態
顯然是一個危險的操作! 要防止這一點
需要在類裡增加如下方法
private final void readObject(ObjectInputStream in)
throws java
io
IOException {
throw new java
io
IOException(
Class cannot be deserialized
);
}
不要通過名稱來比較類
畢竟攻擊者可以用相同的名稱定義類
而且一不小心就會授予這些類不恰當的權限
因此
下面是一個判斷某個對象是否含有某個給定類的錯誤方法的例子
if (obj
getClass()
getName()
equals(
Foo
)) {
如果要判斷兩個對象是否含有完全相同的類
不要對雙方使用getClass()並使用
==
操作符進行比較
而應該使用如下形式
if (a
getClass() == b
getClass()) {
如果確實需要判斷某個對象是否含有某個給定類名
需要嚴格按照規范並確保使用當前名稱空間(當前類的ClassLoader所在名稱空間)
因此
應該使用如下形式
if (obj
getClass() == this
getClassLoader()
loadClass(
Foo
)) {
本原則來自McGraw和Felten
而且確實是個好原則
要補充的是
盡可能地避免比較類值通常是個好注意
通常最好是盡力設計類的方法和接口
從而完全不必要做這些事
盡管如此
實際上無法完全做到
所以知道這些技巧還是很重要的
不要把秘密(密鑰
密碼或算法)存儲在代碼或數據裡
有惡意的JVM可以迅速看到這一數據
打亂代碼並不能在認真的攻擊者面前實際隱藏代碼
From:http://tw.wingwit.com/Article/program/Java/gj/201311/27480.html