CodeGym /Blog Java /Random-FR /Sécurité en Java : bonnes pratiques
John Squirrels
Niveau 41
San Francisco

Sécurité en Java : bonnes pratiques

Publié dans le groupe Random-FR
L'une des métriques les plus importantes dans les applications serveur est la sécurité. Il s'agit d'un type d' exigence non fonctionnelle . Sécurité en Java : bonnes pratiques - 1La sécurité comprend de nombreux composants. Bien sûr, il faudrait plus d'un article pour couvrir pleinement tous les principes de sécurité et mesures de sécurité connus, nous nous attarderons donc sur les plus importants. Une personne connaissant bien ce sujet peut mettre en place tous les processus pertinents, éviter de créer de nouvelles failles de sécurité et sera nécessaire dans n'importe quelle équipe. Bien sûr, vous ne devez pas penser que votre application sera sécurisée à 100 % si vous suivez ces pratiques. Non! Mais ce sera certainement plus sûr avec eux. Allons-y.

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

Tout d'abord, la sécurité en Java commence au niveau des capacités du langage. Que ferions-nous s'il n'y avait pas de modificateurs d'accès ? Il n'y aurait plus que l'anarchie. Le langage de programmation nous aide à écrire du code sécurisé et utilise également de nombreuses fonctionnalités de sécurité implicites :
  1. Dactylographie forte. Java est un langage typé statiquement. Cela permet d'intercepter les erreurs liées au type lors de l'exécution.
  2. Modificateurs d'accès. Ceux-ci nous permettent de personnaliser l'accès aux classes, aux méthodes et aux champs selon les besoins.
  3. Gestion automatique de la mémoire. Pour cela, les développeurs Java disposent d'un ramasse-miettes qui nous évite d'avoir à tout configurer manuellement. Oui, des problèmes surviennent parfois.
  4. Vérification du bytecode : Java est compilé en bytecode, qui est vérifié par le runtime avant d'être exécuté.
En outre, il existe des recommandations de sécurité d'Oracle . Bien sûr, il n'est pas écrit dans un langage élevé et vous pourriez vous endormir plusieurs fois en le lisant, mais cela en vaut la peine. En particulier, le document intitulé Secure Coding Guidelines for Java SE est important. Il fournit des conseils sur la façon d'écrire du code sécurisé. Ce document contient une énorme quantité d'informations très utiles. Si vous en avez l'occasion, vous devriez absolument le lire. Pour attiser votre intérêt pour ce matériel, voici quelques conseils intéressants :
  1. Évitez de sérialiser les classes sensibles à la sécurité. La sérialisation expose l'interface de classe dans le fichier sérialisé, sans parler des données sérialisées.
  2. Essayez d'éviter les classes mutables pour les données. Cela offre tous les avantages des classes immuables (par exemple, la sécurité des threads). Si vous avez un objet mutable, cela peut entraîner un comportement inattendu.
  3. Faites des copies des objets mutables retourné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…
Fondamentalement, Secure Coding Guidelines for Java SE est une collection de trucs et astuces sur la façon d'écrire du code Java correctement et en toute sécurité.

2. Éliminer les vulnérabilités d'injection SQL

Il s'agit d'un type particulier de vulnérabilité. C'est spécial parce que c'est à la fois l'une des vulnérabilités les plus célèbres et l'une des plus courantes. Si vous ne vous êtes jamais intéressé à la sécurité informatique, vous ne le saurez pas. Qu'est-ce que l'injection SQL ? Il s'agit d'une attaque de base de données qui consiste à injecter du code SQL supplémentaire là où il n'est pas attendu. Supposons que nous ayons une méthode qui accepte une sorte de paramètre pour interroger la base de données. Par exemple, un nom d'utilisateur. Un code vulnérable ressemblerait à ceci :

// 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
}
Dans cet exemple, une requête SQL est préparée à l'avance sur une ligne distincte. Alors, quel est 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? Eh bien, quoi alors? Mettons-nous à la place d'un testeur et réfléchissons à ce qui pourrait être passé comme valeur de firstName . Par exemple:
  1. Nous pouvons passer ce qui est attendu - un nom d'utilisateur. Ensuite, la base de données renverra tous les utilisateurs portant ce nom.
  2. Nous pouvons passer une chaîne vide. Ensuite, tous les utilisateurs seront renvoyés.
  3. Mais on peut aussi passer ce qui suit : "'; DROP TABLE USERS;". Et là, nous avons maintenant d'énormes problèmes. Cette requête supprimera une table de la base de données. Avec toutes les données. TOUT.
Pouvez-vous imaginer les problèmes que cela causerait? Au-delà, vous pouvez écrire ce que vous voulez. Vous pouvez modifier les noms de tous les utilisateurs. Vous pouvez supprimer leurs adresses. Les possibilités de sabotage sont immenses. Pour éviter cela, vous devez empêcher l'injection d'une requête toute faite et plutôt former la requête à l'aide de paramètres. Cela devrait être le seul moyen de créer des requêtes de base de données. C'est ainsi que vous pouvez éliminer cette vulnérabilité. Par exemple:

// 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
}
De cette façon, la vulnérabilité est évitée. Pour ceux qui veulent approfondir cet article, voici un excellent exemple . Comment savez-vous quand vous comprenez cette vulnérabilité ? Si vous obtenez la blague dans la bande dessinée ci-dessous, alors vous avez probablement une idée claire de ce qu'est cette vulnérabilité :DSécurité en Java : bonnes pratiques - 2

3. Analysez les dépendances et maintenez-les à jour

Qu'est-ce que cela signifie? Si vous ne savez pas ce qu'est une dépendance, je vais vous expliquer. Une dépendance est une archive JAR avec du code qui est connecté à un projet utilisant des systèmes de construction automatiques (Maven, Gradle, Ant) afin de réutiliser la solution de quelqu'un d'autre. Par exemple, Project Lombok , qui génère des getters, des setters, etc. pour nous dans le runtime. Les grandes applications peuvent avoir de très nombreuses dépendances. Certaines sont transitives (c'est-à-dire que chaque dépendance peut avoir ses propres dépendances, etc.). En conséquence, les attaquants prêtent de plus en plus attention aux dépendances open source, car elles sont régulièrement utilisées et de nombreux clients peuvent avoir des problèmes à cause d'elles. Il est important de s'assurer qu'il n'y a pas de vulnérabilités connues dans l'ensemble de l'arborescence des dépendances (oui, cela ressemble à un arbre). Il y a plusieurs moyens de le faire.

Utilisez Snyk pour la surveillance des dépendances

Snyk vérifie toutes les dépendances du projet et signale les vulnérabilités connues. Vous pouvez vous inscrire sur Snyk et importer vos projets via GitHub. Sécurité en Java : bonnes pratiques - 3De plus, comme vous pouvez le voir sur l'image ci-dessus, si une vulnérabilité est corrigée dans une version plus récente, Snyk proposera le correctif et créera une demande d'extraction. Vous pouvez l'utiliser gratuitement pour des projets open source. Les projets sont scannés à intervalles réguliers, par exemple une fois par semaine, une fois par mois. J'ai enregistré et ajouté tous mes référentiels publics à l'analyse Snyk (il n'y a rien de dangereux à cela, car ils sont déjà publics pour tout le monde). Snyk a ensuite 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 demandes d'extraction dans des projets où les dépendances doivent être mises à jour : Sécurité en Java : bonnes pratiques - 5Et aussi :Sécurité en Java : bonnes pratiques - 6C'est un excellent outil pour trouver des vulnérabilités et surveiller les mises à jour des nouvelles versions.

Utiliser le laboratoire de sécurité GitHub

Toute personne travaillant sur GitHub peut profiter de ses outils intégrés. Vous pouvez en savoir plus sur cette approche dans leur article de blog intitulé Annoncing GitHub Security Lab . Cet outil, bien sûr, est plus simple que Snyk, mais vous ne devriez certainement pas le négliger. De plus, le nombre de vulnérabilités connues ne fera qu'augmenter, donc Snyk et GitHub Security Lab continueront à se développer et à s'améliorer.

Activer Sonatype DepShield

Si vous utilisez GitHub pour stocker vos référentiels, vous pouvez ajouter Sonatype DepShield, l'une des applications du MarketPlace, à vos projets. Il peut également être utilisé pour analyser les projets à la recherche de dépendances. De plus, s'il trouve quelque chose, un problème GitHub sera généré avec une description appropriée, comme indiqué ci-dessous :Sécurité en Java : bonnes pratiques - 7

4. Manipulez les données confidentielles avec soin

Nous pourrions également utiliser l'expression "données sensibles". La divulgation des informations personnelles d'un client, des numéros de carte de crédit et d'autres informations sensibles peut causer un préjudice irréparable. Tout d'abord, examinez attentivement la conception de votre application et déterminez si vous avez vraiment besoin de telle ou telle donnée. Peut-être n'avez-vous pas réellement besoin de certaines des données dont vous disposez - des données qui ont été ajoutées pour un avenir qui n'est pas venu et qui ne se produira probablement pas. De plus, vous divulguez souvent ces données par inadvertance via la journalisation. 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 vous évitera d'afficher accidentellement des champs confidentiels. Si vous utilisez Lombok pour générer le toString(), vous pouvez utiliser l' annotation @ToString.Exclude pour empêcher l'utilisation d'un champ dans la sortie de la méthode toString() . Soyez également très prudent lorsque vous envoyez des données vers le monde extérieur. Supposons que nous ayons un point de terminaison HTTP qui affiche les noms de tous les utilisateurs. Il n'est pas nécessaire de montrer l'identifiant interne unique d'un utilisateur. Pourquoi? Parce qu'un attaquant pourrait l'utiliser pour obtenir d'autres informations plus sensibles sur l'utilisateur. Par exemple, si vous utilisez Jackson pour sérialiser/désérialiser un POJO vers/depuis JSON , vous pouvez utiliser @JsonIgnore et @JsonIgnorePropertiesannotations pour empêcher la sérialisation/désérialisation de champs spécifiques. En général, vous devez utiliser différentes classes POJO à différents endroits. Qu'est-ce que cela signifie?
  1. Lorsque vous travaillez avec une base de données, utilisez un type de POJO (une entité).
  2. Lorsque vous travaillez avec une logique métier, convertissez une entité en modèle.
  3. Lorsque vous travaillez avec le monde extérieur et que vous envoyez des requêtes HTTP, utilisez différentes entités (DTO).
De cette façon, vous pouvez clairement définir 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, nous devons utiliser le cryptage. En fonction de la tâche, vous devez décider du type de chiffrement à utiliser. De plus, un cryptage plus fort prend plus de temps, donc encore une fois, vous devez considérer à quel point le besoin en justifie le temps passé dessus. Bien sûr, vous pouvez écrire vous-même un algorithme de chiffrement. Mais ce n'est pas nécessaire. Vous pouvez utiliser les 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 ce qu'il faut faire, en utilisant cet exemple impliquant le chiffrement et le déchiffrement :

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

Cryptage des mots de passe

Pour cette tâche, il est plus sûr d'utiliser le chiffrement asymétrique. Pourquoi? Parce que l'application n'a pas vraiment besoin de déchiffrer les mots de passe. C'est l'approche standard. En réalité, lorsqu'un utilisateur saisit un mot de passe, le système le crypte et le compare avec ce qui existe dans le magasin de mots de passe. Le même processus de cryptage est effectué, nous pouvons donc nous attendre à ce qu'ils correspondent, si le mot de passe correct est entré, bien sûr :) BCrypt et SCrypt conviennent ici. Les deux sont des fonctions à sens unique (hachages cryptographiques) avec des algorithmes de calcul complexes qui prennent beaucoup de temps. C'est exactement ce dont nous avons besoin, car les calculs directs prendront une éternité (enfin, très longtemps). Spring Security prend en charge toute une gamme d'algorithmes. Nous pouvons utiliser SCryptPasswordEncoder et BCryptPasswordEncoder. Ce qui est actuellement considéré comme un algorithme de chiffrement fort pourrait être jugé faible l'année prochaine. En conséquence, nous concluons que nous devons vérifier régulièrement les algorithmes que nous utilisons et, au besoin, mettre à jour les bibliothèques contenant les algorithmes de chiffrement.

Au lieu d'une conclusion

Aujourd'hui, nous avons parlé de sécurité et, naturellement, beaucoup de choses sont restées en coulisses. Je viens de vous ouvrir la porte d'un nouveau monde, un monde qui a sa propre vie. La sécurité, c'est comme la politique : si vous ne vous occupez pas de politique, la politique s'occupera de vous. Je vous propose traditionnellement de me suivre sur le compte GitHub . J'y poste mes créations impliquant diverses technologies que j'étudie et applique au travail.

Liens utiles

  1. Guru99 : Tutoriel d'injection SQL
  2. Oracle : Centre de ressources sur la sécurité Java
  3. Oracle : directives de codage sécurisé pour Java SE
  4. Baeldung : les bases de la sécurité Java
  5. Moyen : 10 conseils pour renforcer votre sécurité Java
  6. Snyk : 10 bonnes pratiques de sécurité Java
  7. GitHub : Annonce du GitHub Security Lab : sécurisation du code mondial, ensemble
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION