JavaRush /Blog Java /Random-VI /Bảo mật trong Java: các phương pháp hay nhất
Roman Beekeeper
Mức độ

Bảo mật trong Java: các phương pháp hay nhất

Xuất bản trong nhóm
Trong các ứng dụng máy chủ, một trong những chỉ số quan trọng nhất là bảo mật. Đây là một trong những loại Yêu cầu phi chức năng . Bảo mật trong Java: các phương pháp hay nhất - 1Bảo mật có nhiều thành phần. Tất nhiên, để bao quát đầy đủ tất cả các nguyên tắc và hành động bảo vệ đã biết đó, bạn cần phải viết nhiều bài, vì vậy hãy tập trung vào bài quan trọng nhất. Một người thông thạo chủ đề này sẽ có thể thiết lập tất cả các quy trình và đảm bảo rằng chúng không tạo ra các lỗ hổng bảo mật mới, sẽ cần thiết trong bất kỳ nhóm nào. Tất nhiên, bạn không nên nghĩ rằng nếu bạn làm theo những nguyên tắc này thì ứng dụng sẽ hoàn toàn an toàn. KHÔNG! Nhưng chắc chắn sẽ an toàn hơn khi ở bên họ. Đi.

1. Cung cấp bảo mật ở cấp độ ngôn ngữ Java

Trước hết, bảo mật trong Java bắt đầu ngay ở cấp tính năng ngôn ngữ. Đây là những gì chúng tôi sẽ làm nếu không có công cụ sửa đổi quyền truy cập?... Tình trạng hỗn loạn, không hơn không kém. Ngôn ngữ lập trình giúp chúng ta viết mã bảo mật và còn tận dụng được nhiều tính năng bảo mật tiềm ẩn:
  1. Gõ mạnh. Java là ngôn ngữ được gõ tĩnh cung cấp khả năng phát hiện lỗi loại trong thời gian chạy.
  2. Truy cập các công cụ sửa đổi. Nhờ chúng, chúng ta có thể định cấu hình quyền truy cập vào các lớp, phương thức và trường lớp theo cách chúng ta cần.
  3. Quản lý bộ nhớ tự động. Vì mục đích này, chúng tôi (những người theo chủ nghĩa Java ;)) có Garbage Collector, giúp bạn thoát khỏi việc cấu hình thủ công. Có, đôi khi có vấn đề phát sinh.
  4. Kiểm tra mã byte: Java biên dịch thành mã byte, mã này được kiểm tra trong thời gian chạy trước khi chạy.
Trong số những thứ khác, có những khuyến nghị từ Oracle về bảo mật. Tất nhiên, nó không được viết theo “văn phong cao siêu” và bạn có thể ngủ quên vài lần khi đọc nó, nhưng nó rất đáng giá. Một tài liệu đặc biệt quan trọng là Nguyên tắc mã hóa an toàn cho Java SE , tài liệu này cung cấp các mẹo về cách viết mã bảo mật. Tài liệu này chứa rất nhiều thông tin hữu ích. Nếu có thể, nó chắc chắn đáng đọc. Để thu hút sự quan tâm đến tài liệu này, đây là một số mẹo thú vị:
  1. Tránh tuần tự hóa các lớp nhạy cảm an toàn. Trong trường hợp này, bạn có thể lấy giao diện lớp từ tệp được tuần tự hóa, chưa kể dữ liệu đang được tuần tự hóa.
  2. Cố gắng tránh các lớp dữ liệu có thể thay đổi. Điều này mang lại tất cả lợi ích của các lớp bất biến (ví dụ: an toàn luồng). Nếu có một đối tượng có thể thay đổi, điều này có thể dẫn đến hành vi không mong muốn.
  3. Tạo bản sao của các đối tượng có thể thay đổi được trả về. Nếu một phương thức trả về một tham chiếu đến một đối tượng có thể thay đổi bên trong thì mã máy khách có thể thay đổi trạng thái bên trong của đối tượng.
  4. Và như thế…
Nhìn chung, Nguyên tắc mã hóa an toàn cho Java SE chứa một tập hợp các mẹo và thủ thuật về cách viết mã trong Java một cách chính xác và an toàn.

2. Loại bỏ lỗ hổng SQL SQL

Lỗ hổng độc nhất. Tính độc đáo của nó nằm ở chỗ nó vừa là một trong những lỗ hổng nổi tiếng nhất vừa là một trong những lỗ hổng phổ biến nhất. Nếu bạn không quan tâm đến vấn đề bảo mật thì bạn sẽ không biết về nó. SQL SQL là gì? Đây là một cuộc tấn công vào cơ sở dữ liệu bằng cách chèn thêm mã SQL vào nơi không mong đợi. Giả sử chúng ta có một phương thức lấy một số tham số để truy vấn cơ sở dữ liệu. Ví dụ: tên người dùng. Mã có lỗ hổng sẽ trông giống như thế này:
// Метод достает из базы данных всех пользователей с определенным именем
public List<User> findByFirstName(String firstName) throws SQLException {
   // Создается связь с базой данных
   Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);

   // Пишем sql request в базу данных с нашим firstName
   String query = "SELECT * FROM USERS WHERE firstName = " + firstName;

   // выполняем request
   Statement statement = connection.createStatement();
   ResultSet result = statement.executeQuery(query);

   // при помощи mapToUsers переводит ResultSet в коллекцию юзеров.
   return mapToUsers(result);
}

private List<User> mapToUsers(ResultSet resultSet) {
   //переводит в коллекцию юзеров
}
Trong ví dụ này, truy vấn sql được chuẩn bị trước trên một dòng riêng. Có vẻ như vấn đề là gì, phải không? Có lẽ vấn đề là sử dụng nó sẽ tốt hơn String.format? KHÔNG? Vậy thì sao? Hãy đặt mình vào vị trí của người thử nghiệm và suy nghĩ về những gì có thể truyền tải trong giá trị firstName. Ví dụ:
  1. Bạn có thể chuyển những gì được mong đợi - tên người dùng. Sau đó cơ sở dữ liệu sẽ trả về tất cả người dùng có tên đó.
  2. Bạn có thể truyền một chuỗi trống: sau đó tất cả người dùng sẽ được trả về.
  3. Hoặc bạn có thể chuyển thông tin sau: “''; NGƯỜI DÙNG BẢNG THẢ;”. Và ở đây sẽ có những vấn đề lớn hơn. Truy vấn này sẽ xóa bảng khỏi cơ sở dữ liệu. Với tất cả dữ liệu. MỌI NGƯỜI.
Bạn có thể tưởng tượng điều này có thể gây ra những vấn đề gì không? Sau đó bạn có thể viết bất cứ điều gì bạn muốn. Bạn có thể thay đổi tên của tất cả người dùng, bạn có thể xóa địa chỉ của họ. Phạm vi phá hoại là rất lớn. Để tránh điều này, bạn cần ngừng đưa vào một truy vấn được tạo sẵn và thay vào đó hãy xây dựng truy vấn đó bằng cách sử dụng các tham số. Đây phải là cách duy nhất để truy vấn cơ sở dữ liệu. Bằng cách này bạn có thể loại bỏ lỗ hổng này. Ví dụ:
// Метод достает из базы данных всех пользователей с определенным именем
public List<User> findByFirstName(String firstName) throws SQLException {
   // Создается связь с базой данных
   Connection connection = DriverManager.getConnection(DB_URL, USER, PASS);

   // Создаем параметризированный request.
   String query = "SELECT * FROM USERS WHERE firstName = ?";

   // Создаем подготовленный стейтмент с параметризованным requestом
   PreparedStatement statement = connection.prepareStatement(query);

   // Передаем meaning параметра
   statement.setString(1, firstName);

   // выполняем request
   ResultSet result = statement.executeQuery(query);

   // при помощи mapToUsers переводим ResultSet в коллекцию юзеров.
   return mapToUsers(result);
}

private List<User> mapToUsers(ResultSet resultSet) {
   //переводим в коллекцию юзеров
}
Bằng cách này, lỗ hổng này có thể tránh được. Đối với những người muốn tìm hiểu sâu hơn về bài viết này, đây là một ví dụ tuyệt vời . Làm thế nào để bạn biết nếu bạn hiểu phần này? Nếu trò đùa dưới đây trở nên rõ ràng thì đây là dấu hiệu chắc chắn rằng bản chất của lỗ hổng đã rõ ràng :D Bảo mật trong Java: các phương pháp hay nhất - 2

3. Quét và cập nhật các phần phụ thuộc

Nó có nghĩa là gì? Đối với những người không biết phụ thuộc là gì, tôi sẽ giải thích: đó là một kho lưu trữ jar có mã được kết nối với một dự án bằng hệ thống xây dựng tự động (Maven, Gradle, Ant) để sử dụng lại giải pháp của người khác. Ví dụ: Project Lombok , tạo ra getters, setters, v.v. cho chúng ta trong thời gian chạy. Và nếu chúng ta nói về các ứng dụng lớn, chúng sử dụng nhiều phần phụ thuộc khác nhau. Một số có tính bắc cầu (nghĩa là mỗi phần phụ thuộc có thể có các phần phụ thuộc riêng, v.v.). Do đó, những kẻ tấn công ngày càng chú ý đến các phụ thuộc nguồn mở, vì chúng được sử dụng thường xuyên và có thể gây ra sự cố cho nhiều khách hàng. Điều quan trọng là phải đảm bảo rằng không có lỗ hổng nào đã biết trong toàn bộ cây phụ thuộc (chính xác là nó trông như thế). Và có một số cách để làm điều này.

Sử dụng Snyk để theo dõi

Công cụ Snyk kiểm tra tất cả các phần phụ thuộc của dự án và gắn cờ các lỗ hổng đã biết. Ví dụ: ở đó bạn có thể đăng ký và nhập các dự án của mình thông qua GitHub. Bảo mật trong Java: các phương pháp hay nhất - 3Ngoài ra, như bạn có thể thấy trong hình trên, nếu phiên bản mới hơn có giải pháp cho lỗ hổng này, Snyk sẽ đề nghị làm như vậy và tạo Pull-Request. Nó có thể được sử dụng miễn phí cho các dự án nguồn mở. Các dự án sẽ được quét theo tần suất nào đó: mỗi tuần một lần, mỗi tháng một lần. Tôi đã đăng ký và thêm tất cả các kho lưu trữ công khai của mình vào bản quét Snyk (không có gì nguy hiểm về việc này: chúng đã được mở cho tất cả mọi người). Tiếp theo, Snyk hiển thị kết quả quét: Bảo mật trong Java: các phương pháp hay nhất - 4Và sau một thời gian, Snyk-bot đã chuẩn bị một số Yêu cầu kéo trong các dự án cần cập nhật phần phụ thuộc: Bảo mật trong Java: các phương pháp hay nhất - 5Và đây là một kết quả khác: Bảo mật trong Java: các phương pháp hay nhất - 6Vì vậy, đây là một công cụ tuyệt vời để tìm kiếm lỗ hổng và giám sát việc cập nhật các phiên bản mới.

Sử dụng Phòng thí nghiệm bảo mật GitHub

Những người làm việc trên GitHub cũng có thể tận dụng các công cụ tích hợp sẵn của họ. Bạn có thể đọc thêm về phương pháp này trong bản dịch của tôi từ blog của họ Thông báo về Phòng thí nghiệm bảo mật GitHub . Tất nhiên, công cụ này đơn giản hơn Snyk nhưng bạn nhất định không nên bỏ qua nó. Ngoài ra, số lượng lỗ hổng đã biết sẽ ngày càng tăng, vì vậy cả Snyk và GitHub Security Lab sẽ mở rộng và cải thiện.

Kích hoạt Sonatype DepShield

Nếu bạn sử dụng GitHub để lưu trữ kho lưu trữ của mình, bạn có thể thêm một trong các ứng dụng vào dự án của mình từ MarketPlace - Sonatype DepShield. Với sự trợ giúp của nó, bạn cũng có thể quét các dự án để tìm phần phụ thuộc. Hơn nữa, nếu tìm thấy thứ gì đó, Vấn đề GitHub sẽ được tạo với mô tả tương ứng, như hiển thị bên dưới: Bảo mật trong Java: các phương pháp hay nhất - 7

4. Xử lý dữ liệu nhạy cảm một cách cẩn thận

Bảo mật trong Java: các phương pháp hay nhất - 8Trong tiếng Anh, cụm từ “dữ liệu nhạy cảm” phổ biến hơn. Việc tiết lộ thông tin cá nhân, số thẻ tín dụng và các thông tin cá nhân khác của khách hàng có thể gây ra tổn hại không thể khắc phục được. Trước hết, bạn cần xem xét kỹ thiết kế ứng dụng và xác định xem có thực sự cần dữ liệu nào không. Có lẽ một số trong số đó không cần thiết, nhưng chúng được thêm vào cho một tương lai chưa đến và khó có thể đến. Ngoài ra, trong quá trình ghi nhật ký dự án, dữ liệu đó có thể bị rò rỉ. Một cách đơn giản để ngăn dữ liệu nhạy cảm xâm nhập vào nhật ký của bạn là dọn sạch các phương thức toString()của thực thể miền (chẳng hạn như Người dùng, Học sinh, Giáo viên, v.v.). Điều này sẽ ngăn trường hợp các trường nhạy cảm bị vô tình in ra. Nếu bạn sử dụng Lombok để tạo một phương thức toString(), bạn có thể sử dụng chú thích @ToString.Excludeđể ngăn không cho trường này được sử dụng trong đầu ra thông qua phương thức đó toString(). Ngoài ra, hãy hết sức cẩn thận khi chia sẻ dữ liệu với thế giới bên ngoài. Ví dụ: có một điểm cuối http hiển thị tên của tất cả người dùng. Không cần hiển thị ID duy nhất nội bộ của người dùng. Tại sao? Bởi vì bằng cách sử dụng nó, kẻ tấn công có thể lấy được thông tin khác bí mật hơn về mỗi người dùng. Ví dụ: nếu bạn sử dụng Jackson để tuần tự hóa và giải tuần tự hóa các POJO thành JSON , bạn có thể sử dụng các chú thích @JsonIgnore@JsonIgnorePropertiesđể ngăn các trường cụ thể được tuần tự hóa và giải tuần tự hóa. Nói chung, bạn cần sử dụng các lớp POJO khác nhau cho những địa điểm khác nhau. Nó có nghĩa là gì?
  1. Để làm việc với cơ sở dữ liệu, chỉ sử dụng POJO - Entity.
  2. Để làm việc với logic nghiệp vụ, hãy chuyển Thực thể sang Mô hình.
  3. Để làm việc với thế giới bên ngoài và gửi yêu cầu http, hãy sử dụng thực thể thứ ba - DTO.
Bằng cách này, bạn có thể xác định rõ ràng trường nào sẽ hiển thị từ bên ngoài và trường nào sẽ không.

Sử dụng thuật toán mã hóa và băm mạnh

Dữ liệu bí mật của khách hàng phải được lưu trữ an toàn. Để làm điều này, bạn cần sử dụng mã hóa. Tùy thuộc vào nhiệm vụ, bạn cần quyết định sử dụng loại mã hóa nào. Hơn nữa, mã hóa mạnh hơn sẽ mất nhiều thời gian hơn, vì vậy một lần nữa bạn cần xem xét nhu cầu về nó có phù hợp với thời gian dành cho nó hay không. Tất nhiên, bạn có thể tự viết thuật toán. Nhưng điều này là không cần thiết. Bạn có thể tận dụng các giải pháp hiện có trong lĩnh vực này. Ví dụ: Google 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>
Hãy xem cách sử dụng nó, sử dụng ví dụ về cách mã hóa theo cách này và cách khác:
private static void encryptDecryptExample() {
   AeadConfig.register();
   KeysetHandle handle = KeysetHandle.generateNew(AeadKeyTemplates.AES128_CTR_HMAC_SHA256);

   String plaintext = "Цой жив!";
   String aad = "Юрий Клинских";

   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));
}

Mã hóa mật khẩu

Đối với nhiệm vụ này, cách an toàn nhất là sử dụng mã hóa bất đối xứng. Tại sao? Bởi vì ứng dụng thực sự không cần phải giải mã lại mật khẩu. Đây là cách tiếp cận chung. Trong thực tế, khi người dùng nhập mật khẩu, hệ thống sẽ mã hóa nó và so sánh nó với mật khẩu trong kho mật khẩu. Quá trình mã hóa được thực hiện bằng cách sử dụng cùng một phương tiện, vì vậy bạn có thể mong đợi chúng khớp với nhau (tất nhiên nếu bạn nhập đúng mật khẩu;)). BCrypt và SCrypt phù hợp cho mục đích này. Cả hai đều là hàm một chiều (băm mật mã) với các thuật toán tính toán phức tạp, mất nhiều thời gian. Đây chính xác là những gì bạn cần, vì việc giải mã nó sẽ mất rất nhiều thời gian. Ví dụ: Spring Security hỗ trợ một loạt thuật toán. SCryptPasswordEncoderBạn cũng có thể dùng BCryptPasswordEncoder. Một thuật toán mã hóa mạnh bây giờ có thể yếu vào năm tới. Do đó, chúng tôi kết luận rằng cần phải kiểm tra các thuật toán được sử dụng và cập nhật các thư viện bằng thuật toán.

Thay vì đầu ra

Hôm nay chúng ta đã nói về sự an toàn và tất nhiên, nhiều thứ đã bị bỏ lại phía sau. Tôi vừa mở cánh cửa đến một thế giới mới cho bạn: một thế giới sống cuộc sống của riêng mình. Với an ninh cũng giống như với chính trị: nếu bạn không tham gia vào chính trị, chính trị sẽ tham gia vào bạn. Theo truyền thống, tôi khuyên bạn nên đăng ký tài khoản Github của mình . Ở đó tôi đăng công việc của mình về nhiều công nghệ khác nhau mà tôi nghiên cứu và áp dụng trong công việc.

Liên kết hữu ích

Có, hầu hết tất cả các bài viết trên trang đều được viết bằng tiếng Anh. Dù muốn hay không thì tiếng Anh vẫn là ngôn ngữ để các lập trình viên giao tiếp. Tất cả các bài báo, sách và tạp chí mới nhất về lập trình đều được viết bằng tiếng Anh. Đó là lý do tại sao các liên kết đến các đề xuất của tôi chủ yếu bằng tiếng Anh:
  1. Habr: SQL SQL cho người mới bắt đầu
  2. Oracle: Trung tâm tài nguyên bảo mật Java
  3. Oracle: Nguyên tắc mã hóa an toàn cho Java SE
  4. Baeldung: Khái niệm cơ bản về bảo mật Java
  5. Trung bình: 10 mẹo để tăng cường bảo mật Java của bạn
  6. Snyk: 10 phương pháp hay nhất về bảo mật java
  7. JR: Thông báo của GitHub Security Lab: cùng nhau bảo vệ tất cả mã của bạn
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION