本節書摘來異步社群《java編碼指南:編寫安全可靠程式的75條建議》一書中的第1章,第1.1節,作者:【美】fred long(弗雷德•朗), dhruv mohindra(德魯•莫欣達), robert c.seacord(羅伯特 c.西科德), dean f.sutherland(迪恩 f.薩瑟蘭), david svoboda(大衛•斯沃博達),更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。
記憶體中的敏感資訊很容易受到攻擊,導緻洩漏。對于以下條件,不管應用程式滿足哪一個,能在應用程式所在的系統上執行代碼的攻擊者,都能通路這些資料。
使用對象來存儲敏感資料,但是内容在使用後沒有被清除或沒有被垃圾收集器回收。
具有可以被作業系統按需(例如,為了執行記憶體管理任務或者為了支援系統休眠)交換到磁盤的記憶體頁面。
在緩沖區(如bufferedreader)中有敏感資料。這些緩沖區持有敏感資料在作業系統緩存或記憶體中的副本。
使用了一些反射機制來控制敏感資料的流動,這些反射技巧可能規避掉系統對該資料的生命周期限制。
通過調試資訊、日志檔案、環境變量、線程轉儲和核心轉儲等方式暴露敏感資料。
如果記憶體中包含的敏感資料在使用後沒有及時被清除,那麼這些資料将極有可能被洩露。為了降低敏感資料洩露的風險,程式必須盡可能地最小化敏感資料的生命周期。
完全緩解(即對記憶體資料萬無一失的保護)需要底層作業系統和java虛拟機的支援。例如,如果将敏感資料交換至磁盤是一個問題,那麼就需要有一個禁用交換和休眠的安全作業系統。
違規代碼示例
在以下違規代碼示例中,程式從控制台讀取使用者名和密碼資訊,并将密碼存儲在一個string對象中。在垃圾收集器回收這個string對象關聯的記憶體之前,憑證資訊會一直處于暴露狀态。
class password {
public static void main (string args[]) throws ioexception {
console c = system.console();
if (c == null) {
system.err.println("no console.");
system.exit(1);
}
string username = c.readline("enter your user name: ");
char[] password = c.readpassword("enter your password: ");
if (!verify(username, password)) {
throw new securityexception("invalid credentials");
// clear the password
arrays.fill(password, ' ');
}
// dummy verify method, always returns true
private static final boolean verify(string username,
char[] password) {
return true;
}<code>`</code>
console.readpassword()方法允許密碼以字元序列的形式傳回,而不是以string對象的形式。是以,程式員可以在使用密碼後立即将其從數組中清除。同時這個方法也禁止将密碼輸出到控制台。
下面的違規代碼示例使用了一個bufferedreader來包裝inputstreamreader對象,導緻敏感資料可以從檔案中被讀取。
void readdata(){
bytebuffer buffer = bytebuffer.allocatedirect(16 * 1024);
try (filechannel rdr =
(new fileinputstream("file")).getchannel()) {
while (rdr.read(buffer) > 0) {
// do something with the buffer
buffer.clear();
} catch (throwable e) {
// handle error
注意,必須手動清除緩沖資料,因為垃圾收集器不會回收直接緩沖區。
适用性
對敏感資料生命周期限制失敗,導緻資訊洩露。
本文僅用于學習和交流目的,不代表異步社群觀點。非商業轉載請注明作譯者、出處,并保留本文的原始連結。