JavaRush /Blog Java /Random-FR /Sécurité en Java : bonnes pratiques
Roman Beekeeper
Niveau 35

Sécurité en Java : bonnes pratiques

Publié dans le groupe Random-FR
Dans les applications serveur, l'un des indicateurs les plus importants est la sécurité. C'est l'un des types d' exigences non fonctionnelles . Sécurité en Java : bonnes pratiques - 1La sécurité comporte de nombreux éléments. Bien sûr, pour couvrir pleinement tous ces principes et actions de protection connus, vous devez écrire plus d’un article, alors concentrons-nous sur le plus important. Une personne qui connaît bien ce sujet, sera capable de mettre en place tous les processus et de s'assurer qu'ils ne créent pas de nouvelles failles de sécurité, sera nécessaire dans n'importe quelle équipe. Bien sûr, il ne faut pas penser que si vous suivez ces pratiques, l’application sera totalement sécurisée. Non! Mais ce sera certainement plus sûr avec eux. Aller.

1. Assurer la sécurité au niveau du langage Java

Tout d’abord, la sécurité en Java commence dès le niveau des fonctionnalités du langage. C'est ce que nous ferions s'il n'y avait pas de modificateurs d'accès ?... L'anarchie, rien de moins. Un langage de programmation nous aide à écrire du code sécurisé et à profiter également de nombreuses fonctionnalités de sécurité implicites :
  1. Frappe forte. Java est un langage typé statiquement qui permet de détecter les erreurs de type au moment de l'exécution.
  2. Modificateurs d'accès. Grâce à eux, nous pouvons configurer l'accès aux classes, aux méthodes et aux champs de classe selon nos besoins.
  3. Gestion automatique de la mémoire. Pour cela, nous (Javaists ;)) disposons de Garbage Collector, qui vous libère de la configuration manuelle. Oui, des problèmes surviennent parfois.
  4. Vérification du bytecode : Java compile en bytecode, qui est vérifié par l'exécution avant de l'exécuter.
Entre autres choses, il existe des recommandations d'Oracle sur la sécurité. Bien sûr, ce n’est pas écrit dans un « style élégant » et vous risquez de vous endormir plusieurs fois en le lisant, mais cela en vaut la peine. Un document particulièrement important est le Secure Coding Directives pour Java SE , qui fournit des conseils sur la façon d'écrire du code sécurisé. Ce document contient de nombreuses informations utiles. Si possible, cela vaut vraiment la peine d'être lu. Pour susciter l’intérêt pour ce matériau, voici quelques conseils intéressants :
  1. Évitez de sérialiser des classes sensibles et sécurisées. Dans ce cas, vous pouvez obtenir l'interface de classe à partir du fichier sérialisé, sans parler des données en cours de sérialisation.
  2. Essayez d'éviter les classes de données mutables. Cela donne tous les avantages des classes immuables (par exemple la sécurité des threads). S'il existe un objet mutable, cela peut entraîner un comportement inattendu.
  3. Faites des copies des objets mutables renvoyés. Si une méthode renvoie une référence à un objet mutable interne, le code client peut modifier l'état interne de l'objet.
  4. Et ainsi de suite…
En général, les directives de codage sécurisé pour Java SE contiennent un ensemble de trucs et astuces sur la façon d'écrire du code en Java correctement et en toute sécurité.

2. Éliminer la vulnérabilité d'injection SQL

Vulnérabilité unique. Sa particularité réside dans le fait qu’il s’agit à la fois de l’une des vulnérabilités les plus connues et les plus courantes. Si la question de la sécurité ne vous intéresse pas, vous ne le saurez pas. Qu’est-ce que l’injection SQL ? Il s'agit d'une attaque contre une base de données en injectant du code SQL supplémentaire là où il n'est pas attendu. Disons que nous avons une méthode qui prend un paramètre pour interroger la base de données. Par exemple, nom d'utilisateur. Le code avec la vulnérabilité ressemblera à ceci :
// Метод достает из базы данных всех пользователей с определенным именем
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) {
   //переводит в коллекцию юзеров
}
Dans cet exemple, la requête SQL est préparée à l'avance dans une ligne distincte. Il semblerait que ce soit le problème, n'est-ce pas ? Peut-être que le problème est qu'il serait préférable d'utiliser String.format? Non? Et alors ? Mettons-nous à la place d'un testeur et réfléchissons à ce qui peut être véhiculé dans la valeur firstName. Par exemple:
  1. Vous pouvez transmettre ce qui est attendu : le nom d'utilisateur. Ensuite, la base de données renverra tous les utilisateurs portant ce nom.
  2. Vous pouvez passer une chaîne vide : alors tous les utilisateurs seront renvoyés.
  3. Ou vous pouvez transmettre ce qui suit : « » ; UTILISATEURS DE TABLE DE DÉPÔT ;". Et ici, il y aura de plus gros problèmes. Cette requête supprimera la table de la base de données. Avec toutes les données. TOUT LE MONDE.
Pouvez-vous imaginer les problèmes que cela pourrait causer ? Ensuite, vous pouvez écrire ce que vous voulez. Vous pouvez modifier le nom de tous les utilisateurs, vous pouvez supprimer leurs adresses. Les possibilités de sabotage sont vastes. Pour éviter cela, vous devez arrêter d'injecter une requête prête à l'emploi et la construire à l'aide de paramètres. Cela devrait être le seul moyen d'interroger la base de données. De cette façon, vous pouvez éliminer cette vulnérabilité. Exemple:
// Метод достает из базы данных всех пользователей с определенным именем
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) {
   //переводим в коллекцию юзеров
}
De cette façon, cette vulnérabilité est évitée. Pour ceux qui souhaitent approfondir le problème au-delà de cet article, voici un excellent exemple . Comment savoir si vous comprenez cette partie ? Si la blague ci-dessous est devenue claire, alors c'est un signe certain que l'essence de la vulnérabilité est claire :D Sécurité en Java : bonnes pratiques - 2

3. Analysez et maintenez les dépendances à jour

Qu'est-ce que ça veut dire? Pour ceux qui ne savent pas ce qu'est une dépendance, je vais vous expliquer : c'est une archive jar avec du code qui est connecté à un projet à l'aide de systèmes de build automatiques (Maven, Gradle, Ant) afin de réutiliser la solution de quelqu'un d'autre. Par exemple, le projet Lombok , qui génère pour nous des getters, des setters, etc. au moment de l'exécution. Et si nous parlons de grandes applications, elles utilisent de nombreuses dépendances différentes. Certaines sont transitives (chaque dépendance peut avoir ses propres dépendances, etc.). Par conséquent, les attaquants prêtent de plus en plus attention aux dépendances open source, car elles sont régulièrement utilisées et peuvent causer des problèmes à de nombreux clients. Il est important de s’assurer qu’il n’y a aucune vulnérabilité connue dans l’ensemble de l’arborescence des dépendances (ce qui est exactement ce à quoi elle ressemble). Et il existe plusieurs façons de procéder.

Utilisez Snyk pour la surveillance

L' outil Snyk vérifie toutes les dépendances du projet et signale les vulnérabilités connues. Là, vous pouvez enregistrer et importer vos projets via GitHub, par exemple. Sécurité en Java : bonnes pratiques - 3De plus, comme vous pouvez le voir sur l'image ci-dessus, si une version plus récente propose une solution à cette vulnérabilité, Snyk proposera de le faire et créera une Pull-Request. Il peut être utilisé gratuitement pour des projets open source. Les projets seront analysés à une certaine fréquence : une fois par semaine, une fois par mois. J'ai enregistré et ajouté tous mes référentiels publics au scan Snyk (il n'y a rien de dangereux là-dedans : ils sont déjà ouverts à tous). Ensuite, Snyk a montré le résultat de l'analyse : Sécurité en Java : bonnes pratiques - 4Et après un certain temps, Snyk-bot a préparé plusieurs Pull-Requests dans des projets où les dépendances doivent être mises à jour : Sécurité en Java : bonnes pratiques - 5Et en voici une autre : Sécurité en Java : bonnes pratiques - 6C'est donc un excellent outil pour rechercher des vulnérabilités et surveiller les mises à jour. nouvelles versions.

Utiliser le laboratoire de sécurité GitHub

Ceux qui travaillent sur GitHub peuvent également profiter de leurs outils intégrés. Vous pouvez en savoir plus sur cette approche dans ma traduction de leur blog Annonce du GitHub Security Lab . Cet outil, bien sûr, est plus simple que Snyk, mais il ne faut surtout pas le négliger. De plus, le nombre de vulnérabilités connues ne fera qu'augmenter, de sorte que Snyk et GitHub Security Lab vont se développer et s'améliorer.

Activer Sonatype DepShield

Si vous utilisez GitHub pour stocker vos référentiels, vous pouvez ajouter l'une des applications à vos projets depuis MarketPlace - Sonatype DepShield. Avec son aide, vous pouvez également analyser les projets à la recherche de dépendances. De plus, s'il trouve quelque chose, un problème GitHub sera créé avec une description correspondante, comme indiqué ci-dessous : Sécurité en Java : bonnes pratiques - 7

4. Manipulez les données sensibles avec soin

Sécurité en Java : bonnes pratiques - 8En anglais, l’expression « données sensibles » est plus courante. La divulgation des informations personnelles, des numéros de carte de crédit et d'autres informations personnelles du client peut causer un préjudice irréparable. Tout d’abord, vous devez examiner de près la conception de l’application et déterminer si des données sont réellement nécessaires. Peut-être que certains d’entre eux ne sont pas nécessaires, mais ils ont été ajoutés pour un avenir qui n’est pas encore venu et qui est peu probable. De plus, lors de la journalisation du projet, ces données peuvent fuir. Un moyen simple d'empêcher les données sensibles d'entrer dans vos journaux consiste à nettoyer les méthodes toString()des entités de domaine (telles que Utilisateur, Étudiant, Enseignant, etc.). Cela empêchera les champs sensibles d’être imprimés accidentellement. Si vous utilisez Lombok pour générer une méthode toString(), vous pouvez utiliser une annotation @ToString.Excludepour empêcher l'utilisation du champ dans la sortie via la méthode toString(). Soyez également très prudent lorsque vous partagez des données avec le monde extérieur. Par exemple, il existe un point de terminaison http qui affiche les noms de tous les utilisateurs. Il n'est pas nécessaire d'afficher l'identifiant unique interne de l'utilisateur. Pourquoi? Car en l'utilisant, un attaquant peut obtenir d'autres informations plus confidentielles sur chaque utilisateur. Par exemple, si vous utilisez Jackson pour sérialiser et désérialiser des POJO en JSON , vous pouvez utiliser les annotations @JsonIgnoreet @JsonIgnorePropertiespour empêcher la sérialisation et la désérialisation de champs spécifiques. En général, vous devez utiliser différentes classes POJO pour différents endroits. Qu'est-ce que ça veut dire?
  1. Pour travailler avec la base de données, utilisez uniquement POJO - Entity.
  2. Pour travailler avec la logique métier, transférez Entité vers Modèle.
  3. Pour travailler avec le monde extérieur et envoyer des requêtes http, utilisez des entités tierces - DTO.
De cette façon, vous pouvez définir clairement quels champs seront visibles de l'extérieur et lesquels ne le seront pas.

Utiliser des algorithmes de cryptage et de hachage puissants

Les données confidentielles des clients doivent être stockées en toute sécurité. Pour ce faire, vous devez utiliser le cryptage. En fonction de la tâche, vous devez décider quel type de cryptage utiliser. De plus, un cryptage plus fort prend plus de temps, vous devez donc encore une fois considérer dans quelle mesure sa nécessité justifie le temps passé dessus. Bien entendu, vous pouvez écrire l’algorithme vous-même. Mais ce n’est pas nécessaire. Vous pouvez profiter des solutions existantes dans ce domaine. Par exemple, 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>
Voyons comment l'utiliser, en utilisant l'exemple de la façon de chiffrer dans un sens et dans l'autre :
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));
}

Cryptage du mot de passe

Pour cette tâche, il est plus sûr d’utiliser le chiffrement asymétrique. Pourquoi? Parce que l’application n’a vraiment pas besoin de déchiffrer les mots de passe. C'est l'approche générale. En réalité, lorsqu'un utilisateur saisit un mot de passe, le système le crypte et le compare avec ce qui se trouve dans le coffre-fort de mots de passe. Le cryptage s'effectue par les mêmes moyens, vous pouvez donc vous attendre à ce qu'ils correspondent (si vous saisissez le bon mot de passe ;), bien sûr). BCrypt et SCrypt conviennent à cet effet. Les deux sont des fonctions à sens unique (hachages cryptographiques) avec des algorithmes informatiques complexes qui prennent beaucoup de temps. C’est exactement ce dont vous avez besoin, car le déchiffrer de front prendra une éternité. Par exemple, Spring Security prend en charge une gamme d'algorithmes. SCryptPasswordEncoderVous pouvez aussi utiliser BCryptPasswordEncoder. Ce qui est aujourd’hui un algorithme de chiffrement puissant pourrait être faible l’année prochaine. En conséquence, nous concluons qu’il est nécessaire de vérifier les algorithmes utilisés et de mettre à jour les bibliothèques avec des algorithmes.

Au lieu de la sortie

Aujourd'hui, nous avons parlé de sécurité et, bien sûr, beaucoup de choses ont été laissées en coulisses. Je viens de vous ouvrir la porte d’un nouveau monde : un monde qui vit sa propre vie. En matière de sécurité, c'est la même chose qu'en politique : si vous ne vous engagez pas en politique, la politique s'engagera en vous. Traditionnellement, je suggère de m'abonner à mon compte Github . J'y publie mes travaux sur diverses technologies que j'étudie et que j'applique au travail.

Liens utiles

Oui, presque tous les articles du site sont rédigés en anglais. Que cela nous plaise ou non, l’anglais est la langue de communication des programmeurs. Tous les articles, livres et magazines les plus récents sur la programmation sont rédigés en anglais. C'est pourquoi mes liens vers des recommandations sont principalement en anglais :
  1. Habr : injection SQL pour les débutants
  2. Oracle : Centre de ressources de sécurité Java
  3. Oracle : Directives de codage sécurisé pour Java SE
  4. Baeldung : Les bases de la sécurité Java
  5. Medium : 10 conseils pour renforcer votre sécurité Java
  6. Snyk : 10 bonnes pratiques de sécurité Java
  7. JR : Annonce du GitHub Security Lab : protéger tout votre code ensemble
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION