CodeGym /Java Blog /Toto sisi /Java 中的安全性:最佳實踐
John Squirrels
等級 41
San Francisco

Java 中的安全性:最佳實踐

在 Toto sisi 群組發布
服務器應用程序中最重要的指標之一是安全性。這是一種非功能性需求Java 中的安全性:最佳實踐 - 1安全性包括許多組件。當然,完全涵蓋所有已知的安全原則和安全措施需要不止一篇文章,因此我們將重點介紹最重要的內容。精通此主題的人可以設置所有相關流程,避免產生新的安全漏洞,並且任何團隊都需要他。當然,您不應認為如果您遵循這些做法,您的應用程序就會 100% 安全。不!但是他們肯定會更安全。我們走吧。

1.提供Java語言級別的安全性

首先,Java 中的安全性從語言的功能級別開始。如果沒有訪問修飾符我們會怎麼做?除了無政府狀態,別無他物。編程語言幫助我們編寫安全代碼,還利用了許多隱式安全功能:
  1. 強打字。Java 是一種靜態類型語言。這使得在運行時捕獲與類型相關的錯誤成為可能。
  2. 訪問修飾符。這些允許我們根據需要自定義對類、方法和字段的訪問。
  3. 自動內存管理。為此,Java 開發人員有一個垃圾收集器,使我們不必手動配置所有內容。是的,有時會出現問題。
  4. 字節碼驗證:Java被編譯成字節碼,在執行之前由運行時檢查。
此外,還有Oracle 的安全建議。當然,這本書不是用高深的語言寫成的,讀起來可能會睡著好幾次,但這是值得的。特別是,名為Secure Coding Guidelines for Java SE 的文檔非常重要。它提供有關如何編寫安全代碼的建議。該文檔傳達了大量非常有用的信息。如果你有機會,你一定要讀一讀。為了激發您對此材料的興趣,這裡有一些有趣的提示:
  1. 避免序列化安全敏感類。序列化暴露了序列化文件中的類接口,更不用說序列化的數據了。
  2. 盡量避免數據的可變類。這提供了不可變類的所有好處(例如線程安全)。如果您確實有一個可變對象,它可能會導致意外行為。
  3. 製作返回的可變對象的副本。如果方法返回對內部可變對象的引用,則客戶端代碼可以更改對象的內部狀態。
  4. 等等…
基本上,Java SE 安全編碼指南是有關如何正確、安全地編寫 Java 代碼的提示和技巧的集合。

2.消除SQL注入漏洞

這是一種特殊的漏洞。它很特別,因為它既是最著名的漏洞之一,也是最常見的漏洞之一。如果您從未對計算機安全感興趣,那麼您將不會了解它。什麼是SQL注入?這是一種數據庫攻擊,涉及在不希望的地方注入額外的 SQL 代碼。假設我們有一個方法接受某種參數來查詢數據庫。例如,用戶名。易受攻擊的代碼看起來像這樣:

// This method retrieves from the database all users with a certain name
public List findByFirstName(String firstName) throws SQLException {
   // Connect to the database
   Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);
  
   // Compose a SQL database query with our firstName
   String query = "SELECT * FROM USERS WHERE firstName = " + firstName;
  
   // Execute the query
   Statement statement = connection.createStatement();
   ResultSet result = statement.executeQuery(query);

   // Use mapToUsers to convert the ResultSet into a collection of users.
   return mapToUsers(result);
}

private List mapToUsers(ResultSet resultSet) {
   // Converts to a collection of users
}
在本例中,SQL 查詢是預先在單獨的行中準備的。那有什麼問題,對吧?也許問題是使用String.format會更好?不?那麼,然後呢?讓我們設身處地為測試人員想想什麼可以作為firstName的值傳遞。例如:
  1. 我們可以傳遞預期的內容——用戶名。然後數據庫將返回所有具有該名稱的用戶。
  2. 我們可以傳遞一個空字符串。然後將返回所有用戶。
  3. 但我們也可以傳遞以下內容:“'; DROP TABLE USERS;”。在這裡,我們現在遇到了 huuuuuuge 問題。此查詢將從數據庫中刪除一個表。連同所有數據。所有的。
你能想像這會導致什麼問題嗎?除此之外,你可以寫任何你想寫的。您可以更改所有用戶的名稱。您可以刪除他們的地址。破壞的範圍是巨大的。為避免這種情況,您需要防止注入現成的查詢,而是使用參數形成查詢。這應該是創建數據庫查詢的唯一方法。這就是消除此漏洞的方法。例如:

// This method retrieves from the database all users with a certain name
public List findByFirstName(String firstName) throws SQLException {
   // Connect to the database
   Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);

   // Create a parameterized query.
   String query = "SELECT * FROM USERS WHERE firstName = ?";

   // Create a prepared statement with the parameterized query
   PreparedStatement statement = connection.prepareStatement(query);
  
   // Pass the parameter's value
   statement.setString(1, firstName);

   // Execute the query
   ResultSet result = statement.executeQuery(query);

   // Use mapToUsers to convert the ResultSet into a collection of users.
   return mapToUsers(result);
}

private List mapToUsers(ResultSet resultSet) {
   // Converts to a collection of users
}
這樣就避免了漏洞。對於那些想要更深入地研究本文的人,這裡有一個很好的例子。您如何知道何時了解此漏洞?如果你看到下面漫畫中的笑話,那麼你可能已經清楚地了解這個漏洞是怎麼回事了:DJava 中的安全性:最佳實踐 - 2

3. 掃描依賴並保持更新

這意味著什麼?如果你不知道什麼是依賴,我會解釋。依賴項是一個 JAR 存檔,其中包含使用自動構建系統(Maven、Gradle、Ant)連接到項目的代碼,以便重用其他人的解決方案。比如Project Lombok,它在運行時為我們生成getters、setters等。大型應用程序可能有很多依賴項。有些是可傳遞的(也就是說,每個依賴項可能有自己的依賴項,等等)。因此,攻擊者越來越關注開源依賴項,因為它們經常被使用,許多客戶端可能因此而出現問題。重要的是要確保整個依賴樹(是的,它看起來像一棵樹)中沒有已知的漏洞。做這件事有很多種方法。

使用 Snyk 進行依賴監控

Snyk檢查所有項目依賴項並標記已知漏洞。您可以在 Snyk 上註冊並通過 GitHub 導入您的項目。Java 中的安全性:最佳實踐 - 3此外,如上圖所示,如果新版本修復了漏洞,Snyk 將提供修復並創建拉取請求。您可以免費將其用於開源項目。定期掃描項目,例如每週一次、每月一次。我註冊並將我所有的公共存儲庫添加到 Snyk 掃描中(這沒有什麼危險,因為它們已經對所有人公開)。Snyk 然後顯示掃描結果:Java 中的安全性:最佳實踐 - 4過了一會兒,Snyk-bot 在需要更新依賴項的項目中準備了幾個拉取請求:Java 中的安全性:最佳實踐 - 5還有:Java 中的安全性:最佳實踐 - 6這是查找漏洞和監控新版本更新的好工具。

使用 GitHub 安全實驗室

任何在 GitHub 上工作的人都可以利用其內置工具。您可以在他們名為Announcing GitHub Security Lab的博客文章中閱讀有關此方法的更多信息。這個工具當然比 Snyk 簡單,但您絕對不應該忽視它。更重要的是,已知漏洞的數量只會增加,因此 Snyk 和 GitHub 安全實驗室都將繼續擴展和改進。

啟用 Sonatype DepShield

如果您使用 GitHub 來存儲您的存儲庫,則可以將 Sonatype DepShield(MarketPlace 中的應用程序之一)添加到您的項目中。它還可用於掃描項目的依賴項。此外,如果它找到了什麼,將生成一個 GitHub Issue,並帶有適當的描述,如下所示:Java 中的安全性:最佳實踐 - 7

4. 小心處理機密數據

我們也可以使用短語“敏感數據”。洩露客戶的個人信息、信用卡號碼和其他敏感信息可能會造成無法彌補的傷害。首先,仔細查看您的應用程序的設計並確定您是否真的需要這個或那個數據。也許您實際上並不需要您擁有的某些數據——為尚未到來且不太可能到來的未來添加的數據。此外,許多人無意中通過日誌記錄洩露了此類數據。防止敏感數據進入日誌的一種簡單方法是清除域實體(例如用戶、學生、教師等)的toString()方法。這將防止您意外輸出機密字段。如果您使用 Lombok 生成toString()方法,您可以使用@ToString.Exclude註釋來防止在toString()方法的輸出中使用某個字段。另外,向外界發送數據時要非常小心。假設我們有一個顯示所有用戶名稱的 HTTP 端點。無需顯示用戶的唯一內部 ID。為什麼?因為攻擊者可以使用它來獲取有關用戶的其他更敏感的信息。例如,如果您使用 Jackson 將POJO序列化/反序列化為JSON,那麼您可以使用@JsonIgnore@JsonIgnoreProperties註釋以防止特定字段的序列化/反序列化。一般來說,你需要在不同的地方使用不同的 POJO 類。這意味著什麼?
  1. 使用數據庫時,使用一種類型的 POJO(實體)。
  2. 使用業務邏輯時,將實體轉換為模型。
  3. 在與外界合作並發送 HTTP 請求時,使用不同的實體 (DTO)。
這樣您就可以清楚地定義哪些字段從外部可見,哪些不可見。

使用強大的加密和哈希算法

客戶的機密數據必須安全存儲。為此,我們需要使用加密。根據任務,您需要決定使用哪種類型的加密。此外,更強大的加密需要更多時間,因此您需要再次考慮對它的需求在多大程度上證明花在它上面的時間是合理的。當然,你可以自己寫一個加密算法。但這是不必要的。您可以在這方面使用現有的解決方案。例如,谷歌 Tink

<!-- https://mvnrepository.com/artifact/com.google.crypto.tink/tink -->
<dependency>
   <groupid>com.google.crypto.tink</groupid>
   <artifactid>tink</artifactid>
   <version>1.3.0</version>
</dependency>
讓我們看看要做什麼,使用這個涉及加密和解密的例子:

private static void encryptDecryptExample() {
   AeadConfig.register();
   KeysetHandle handle = KeysetHandle.generateNew(AeadKeyTemplates.AES128_CTR_HMAC_SHA256);

   String plaintext = "Elvis lives!";
   String aad = "Buddy Holly";

   Aead aead = handle.getPrimitive(Aead.class);
   byte[] encrypted = aead.encrypt(plaintext.getBytes(), aad.getBytes());
   String encryptedString = Base64.getEncoder().encodeToString(encrypted);
   System.out.println(encryptedString);

   byte[] decrypted = aead.decrypt(Base64.getDecoder().decode(encrypted), aad.getBytes());
   System.out.println(new String(decrypted));
}

加密密碼

對於這個任務,使用非對稱加密是最安全的。為什麼?因為應用程序並不真正需要解密密碼。這是標準方法。實際上,當用戶輸入密碼時,系統會對其進行加密並將其與密碼存儲中的內容進行比較。執行相同的加密過程,所以我們可以預期它們會匹配,如果輸入正確的密碼,當然 :) BCrypt 和 SCrypt 在這裡很合適。兩者都是單向函數(加密哈希),具有計算複雜的算法,需要很長時間。這正是我們所需要的,因為直接計算將花費很長時間(好吧,很長很長時間)。Spring Security 支持一整套算法。我們可以使用SCryptPasswordEncoderBCryptPasswordEncoder. 目前被認為是強加密算法的算法明年可能會被認為是弱的。因此,我們得出結論,我們應該定期檢查我們使用的算法,並根據需要更新包含加密算法的庫。

而不是結論

今天我們談到了安全,自然而然地,很多事情都被拋在了幕後。我剛剛為你打開了通往新世界的大門,一個擁有自己生命的世界。安全就像政治,你不忙於政治,政治就會忙於你。我傳統上建議您在GitHub 帳戶上關注我。我在那裡發布我的創作,涉及我正在研究和在工作中應用的各種技術。

有用的鏈接

  1. Guru99:SQL 注入教程
  2. Oracle:Java 安全資源中心
  3. Oracle:Java SE 安全編碼指南
  4. Baeldung:Java 安全基礎
  5. 中:增強 Java 安全性的 10 個技巧
  6. Snyk:10 個 Java 安全最佳實踐
  7. GitHub:宣布 GitHub 安全實驗室:共同保護世界代碼
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION