JavaRush /Java 博客 /Random-ZH /Java 安全性:最佳实践
Roman Beekeeper
第 35 级

Java 安全性:最佳实践

已在 Random-ZH 群组中发布
在服务器应用中,最重要的指标之一就是安全性。这是非功能性需求的类型之一。 Java 安全性:最佳实践 - 1安全性有很多组成部分。当然,为了完全涵盖所有已知的保护原则和行动,您需要写不止一篇文章,所以让我们关注最重要的。任何团队都需要一个精通该主题的人,能够设置所有流程并确保它们不会造成新的安全漏洞。当然,您不应该认为如果遵循这些做法,应用程序就会完全安全。不!但和他们在一起肯定会更安全。去。

1.提供Java语言级别的安全性

首先,Java 的安全性是从语言功能级别开始的。如果没有访问修饰符,这就是我们会做的事情?...无政府状态,同样如此。编程语言可以帮助我们编写安全代码并利用许多隐式安全功能:
  1. 强打字。Java是一种静态类型语言,能够在运行时检测类型错误。
  2. 访问修饰符。感谢他们,我们可以按照我们需要的方式配置对类、方法和类字段的访问。
  3. 自动内存管理。为此,我们(Javaists ;))提供了垃圾收集器,它使您无需手动配置。是的,有时会出现问题。
  4. 字节码检查: Java 编译为字节码,在运行之前由运行时检查。
除此之外,Oracle还提出了有关安全性的建议。当然,它写得并不“高调”,你可能会读着读着睡着好几次,但这是值得的。一个特别重要的文档是《Java SE 安全编码指南》,它提供了有关如何编写安全代码的提示。该文档包含许多有用的信息。如果可能的话,这绝对值得一读。为了激发人们对这些材料的兴趣,这里有一些有趣的提示:
  1. 避免序列化安全敏感类。在这种情况下,您可以从序列化文件中获取类接口,更不用说正在序列化的数据了。
  2. 尽量避免可变数据类。这提供了不可变类的所有好处(例如线程安全)。如果存在可变对象,这可能会导致意外的行为。
  3. 复制返回的可变对象。如果方法返回对内部可变对象的引用,则客户端代码可以更改该对象的内部状态。
  4. 等等…
一般来说,Java SE 安全编码指南包含一组有关如何正确、安全地用 Java 编写代码的提示和技巧。

2.消除SQL注入漏洞

独特的脆弱性。它的独特之处在于它既是最著名的漏洞之一,也是最常见的漏洞之一。如果您对安全问题不感兴趣,那么您就不会了解它。什么是SQL注入?这是通过在不期望的地方注入额外的 SQL 代码来对数据库进行的攻击。假设我们有一个方法,它需要一些参数来查询数据库。例如,用户名。存在漏洞的代码如下所示:
// Метод достает из базы данных всех пользователей с определенным именем
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) {
   //переводит в коллекцию юзеров
}
在此示例中,sql 查询是在单独的行中预先准备好的。看来问题是什么,对吧?也许问题是使用会更好String.format?不?然后怎样呢?让我们把自己放在测试人员的位置上,思考一下这个值能够传达什么内容firstName。例如:
  1. 您可以传递预期的内容 - 用户名。然后数据库将返回具有该名称的所有用户。
  2. 您可以传递一个空字符串:然后将返回所有用户。
  3. 或者您可以传递以下内容:“''; 删除表用户;”。这里将会出现更大的问题。此查询将从数据库中删除该表。有了所有的数据。每个人。
你能想象这会导致什么问题吗?然后你就可以写任何你想要的东西。您可以更改所有用户的名称,也可以删除他们的地址。破坏的范围是巨大的。为了避免这种情况,您需要停止注入现成的查询,而是使用参数构建它。这应该是查询数据库的唯一方法。这样就可以消除这个漏洞。例子:
// Метод достает из базы данных всех пользователей с определенным именем
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) {
   //переводим в коллекцию юзеров
}
这样就可以避免这个漏洞。对于那些想要深入研究本文的人,这里有一个很好的例子。你怎么知道你是否理解了这一部分?如果下面的笑话变得清晰,那么这肯定表明该漏洞的本质已经清楚:D Java 安全性:最佳实践 - 2

3.扫描并保持依赖更新

这是什么意思?对于那些不知道什么是依赖项的人,我将解释一下:它是一个包含代码的 jar 存档,使用自动构建系统(Maven、Gradle、Ant)连接到项目,以便重用其他人的解决方案。例如Project Lombok,它在运行时为我们生成 getter、setter 等。如果我们谈论大型应用程序,它们会使用许多不同的依赖项。有些是可传递的(即每个依赖项都可以有自己的依赖项,等等)。因此,攻击者越来越关注开源依赖项,因为它们经常使用并且可能会给许多客户端带来问题。重要的是要确保整个依赖关系树中不存在已知的漏洞(这正是它看起来的样子)。有几种方法可以做到这一点。

使用Snyk进行监控

Snyk工具检查所有项目依赖性并标记已知漏洞。例如,您可以在那里通过 GitHub 注册和导入您的项目。 Java 安全性:最佳实践 - 3另外,从上图可以看出,如果新版本有针对此漏洞的解决方案,Snyk 将提出解决方案并创建 Pull-Request。它可以免费用于开源项目。项目将以某种频率进行扫描:每周一次、每月一次。我注册了所有公共存储库并将其添加到 Snyk 扫描中(这没有什么危险:它们已经向所有人开放)。接下来,Snyk 显示了扫描结果: Java 安全性:最佳实践 - 4过了一会儿,Snyk-bot 在需要更新依赖项的项目中准备了多个 Pull-Request: Java 安全性:最佳实践 - 5这是另一个: Java 安全性:最佳实践 - 6所以这是一个用于搜索漏洞和监控更新的优秀工具新版本。

使用 GitHub 安全实验室

在 GitHub 上工作的人也可以利用他们的内置工具。您可以在我的翻译中从他们的博客GitHub 安全实验室公告 中阅读有关此方法的更多信息。当然,这个工具比 Snyk 更简单,但你绝对不应该忽视它。另外,已知漏洞的数量只会增加,因此 Snyk 和 GitHub 安全实验室都将扩大和改进。

激活 Sonatype DepShield

如果您使用 GitHub 存储存储库,则可以从 MarketPlace - Sonatype DepShield 将应用程序之一添加到您的项目中。在它的帮助下,您还可以扫描项目的依赖关系。此外,如果发现某些内容,将创建一个 GitHub Issue 并带有相应的描述,如下所示: Java 安全性:最佳实践 - 7

4. 谨慎处理敏感数据

Java 安全性:最佳实践 - 8在英语演讲中,“敏感数据”一词更为常见。泄露客户的个人信息、信用卡号和其他个人信息可能会造成无法弥补的损害。首先,您需要仔细查看应用程序设计并确定是否确实需要任何数据。也许其中一些是不需要的,但它们是为了一个尚未到来且不太可能到来的未来而添加的。此外,在项目记录过程中,此类数据可能会泄漏。防止敏感数据进入日志的一个简单方法是清理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 = "Цой жив!";
   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));
}

密码加密

对于此任务,使用非对称加密是最安全的。为什么?因为应用程序确实不需要解密回密码。这是一般方法。实际上,当用户输入密码时,系统会对其进行加密并将其与密码库中的内容进行比较。加密是使用相同的方式进行的,因此您可以期望它们匹配(当然,如果您输入正确的密码;)。BCrypt 和 SCrypt 适合此目的。两者都是单向函数(加密哈希),具有计算复杂的算法,需要花费大量时间。这正是你所需要的,因为正面破译它需要很长时间。例如,Spring Security 支持一系列算法。SCryptPasswordEncoder您也可以使用BCryptPasswordEncoder. 现在强大的加密算法明年可能会变得很弱。因此,我们得出的结论是,有必要检查所使用的算法并更新算法库。

而不是输出

今天我们谈到了安全,当然,很多事情都被抛在了后面。我刚刚为你打开了新世界的大门:一个拥有自己生命的世界。安全问题和政治问题一样:你不参与政治,政治就会参与你。传统上,我建议订阅我的 Github 帐户。在那里,我发布了我在工作中学习和应用的各种技术的作品。

有用的链接

是的,该网站上几乎所有文章都是用英文写的。无论我们喜欢与否,英语都是程序员交流的语言。所有有关编程的最新文章、书籍和杂志都是用英语写的。这就是为什么我的推荐链接大多是英文的:
  1. Habr:针对初学者的 SQL 注入
  2. Oracle:Java 安全资源中心
  3. Oracle:Java SE 安全编码指南
  4. Baeldung:Java 安全基础知识
  5. 中:增强 Java 安全性的 10 个技巧
  6. Snyk:10 个 Java 安全最佳实践
  7. JR:GitHub 安全实验室公告:共同保护您的所有代码
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION