10 bonnes pratiques de sécurité en Java
17 septembre 2019
0 minutes de lectureDans cette édition de notre aide-mémoire, nous nous intéressons à dix bonnes pratiques de sécurité en Java, destinées aux responsables de projets open source comme aux développeurs. Cet aide-mémoire est le fruit d’une collaboration entre Brian Vermeer, Developer Advocate chez Snyk, et Jim Manico, Java Champion et fondateur de Manicode Security.
Nous vous recommandons d’imprimer l’aide-mémoire et de lire également nos explications détaillées sur chacune des 10 bonnes pratiques de sécurité en Java, présentées ci-dessous.
Commençons !
Les problèmes de sécurité en Java
Même si nous cherchons tous à écrire du code de qualité, la sécurité en Java ne fait pas toujours partie des priorités des développeurs. Pourtant, prévenir les problèmes de sécurité en Java devrait être aussi important que rendre votre application Java performante, évolutive et facile à maintenir.
Vous devez également vous tenir au courant des nouvelles vulnérabilités en Java, comme Log4Shell (CVE-2021-44228), divulguée en décembre 2021 et touchant les applications exécutant des versions vulnérables d’Apache Log4j.
Dans cet aide-mémoire, nous abordons 10 problèmes courants de sécurité en Java. Nous vous proposons des conseils pratiques et des exemples pour prévenir ces vulnérabilités courantes dans les applications que vous développez.
1. Paramétrez les requêtes pour prévenir les injections
Dans la version 2017 de la liste OWASP Top 10 des vulnérabilités, les injections arrivaient en tête de liste, en première position cette année-là. Dans un exemple courant d’injection SQL en Java, les paramètres d’une requête SQL sont concaténés naïvement à la partie statique de la requête. Voici un exemple d’exécution SQL non sécurisée en Java, qu’un attaquant pourrait exploiter pour obtenir davantage d’informations que prévu.
Si le paramètre de cet exemple est, par exemple, '' OR 1=1, le résultat contient tous les éléments de la table. Le problème peut être encore plus grave si la base de données prend en charge plusieurs requêtes et que le paramètre est ''; UPDATE USERS SET lastname=''.
Pour prévenir ce risque de sécurité en Java, paramétrez les requêtes à l’aide d’une instruction préparée. C’est la seule méthode à utiliser pour créer des requêtes de base de données. En définissant l’intégralité du code SQL et en transmettant ensuite les paramètres à la requête, le code est plus facile à comprendre. Surtout, en distinguant le code SQL des données des paramètres, vous empêchez toute entrée malveillante de détourner la requête.
Dans l’exemple ci-dessus, l’entrée est associée au type String et fait donc partie du code de la requête. Cette technique empêche le paramètre saisi d’interférer avec le code SQL.
Pour en savoir plus sur la prévention des injections SQL, consultez ce guide pratique : Aide-mémoire sur les injections SQL : 8 bonnes pratiques pour prévenir les attaques par injection SQL
2. Utilisez OpenID Connect avec l’authentification à deux facteurs
La gestion des identités et des accès est complexe, et une authentification défaillante est souvent à l’origine de fuites de données. En fait, ce problème figure en deuxième position de la liste OWASP Top 10 des vulnérabilités et constitue donc un risque majeur pour la sécurité en Java. Lorsque vous créez vous-même un système d’authentification, vous devez tenir compte de nombreux aspects : stockage sécurisé des mots de passe, chiffrement robuste, récupération des identifiants, etc. Dans bien des cas, il est plus simple et plus sûr d’utiliser des solutions existantes comme OpenID Connect. OpenID Connect (OIDC) vous permet d’authentifier des utilisateurs sur différents sites Web et applications. Vous n’avez ainsi plus besoin de posséder et de gérer des fichiers de mots de passe. OpenID Connect est une extension d’OAuth 2.0 qui fournit des informations sur les utilisateurs. Elle ajoute un jeton d’identité en plus du jeton d’accès, ainsi qu’un point de terminaison /userinfo qui fournit des informations supplémentaires. Elle ajoute également une fonctionnalité de découverte des points de terminaison et l’enregistrement dynamique des clients.
La configuration d’OpenID Connect à l’aide de bibliothèques comme Spring Security est simple et courante. Veillez à ce que votre application impose l’authentification à deux facteurs (2FA) ou multifacteur (MFA) afin de renforcer la sécurité de votre système.
En ajoutant les dépendances oauth2-client et Spring Security à votre application Spring Boot, vous pouvez faire appel à des clients tiers tels que Google, Github et Okta pour gérer OIDC. Une fois votre application créée, il vous suffit de la connecter au client de votre choix en le spécifiant dans la configuration de votre application ; il peut s’agir de votre identifiant client et de votre secret client GitHub ou Okta, comme dans l’exemple ci-dessous.
pom.xml
application.yaml
3. Analysez vos dépendances pour détecter les vulnérabilités connues
Il y a de fortes chances que vous ne sachiez pas combien de dépendances directes votre application utilise. Il est également très probable que vous ignoriez le nombre de ses dépendances transitives. C’est souvent le cas, alors même que les dépendances constituent la majeure partie de votre application. Les attaquants ciblent de plus en plus les dépendances open source, car leur réutilisation leur permet de faire de nombreuses victimes. Il est important de vérifier l’absence de vulnérabilités de sécurité connues en Java dans l’arborescence complète des dépendances de votre application.
Snyk teste les artefacts de compilation de votre application et signale les dépendances présentant des vulnérabilités connues. Un tableau de bord vous fournit la liste des vulnérabilités de sécurité en Java présentes dans les packages utilisés par votre application.

Snyk vous propose également des versions plus récentes ou des correctifs pour résoudre vos problèmes de sécurité, au moyen d’une pull request adressée à votre dépôt de code source. Snyk protège aussi votre environnement en testant automatiquement (via des webhooks) toutes les futures pull requests créées sur votre dépôt, afin de vérifier qu’elles n’introduisent aucune nouvelle vulnérabilité connue.
Snyk est disponible via une interface Web et une CLI. Vous pouvez donc l’intégrer à votre environnement CI et le configurer pour interrompre la compilation lorsque des vulnérabilités dépassent le seuil de gravité que vous avez défini.
Utilisez Snyk gratuitement pour vos projets open source ou vos projets privés, avec un nombre limité de tests par mois.
4. Traitez les données sensibles avec précaution
La divulgation de données sensibles, comme les données personnelles ou les numéros de carte bancaire de vos clients, peut être préjudiciable. Mais des cas plus subtils peuvent avoir des conséquences tout aussi graves. Par exemple, l’exposition d’identifiants uniques dans votre système constitue une vulnérabilité de sécurité en Java si ces identifiants permettent de récupérer des données supplémentaires lors d’un autre appel.
Tout d’abord, examinez attentivement la conception de votre application et déterminez si vous avez réellement besoin de ces données. Veillez également à ne pas exposer de données sensibles, par exemple dans les journaux, les fonctions de saisie automatique ou lors de la transmission des données.
Pour éviter que des données sensibles ne se retrouvent dans vos journaux, vous pouvez facilement assainir les méthodes toString() de vos entités de domaine. Vous éviterez ainsi d’afficher accidentellement des champs sensibles. Si vous utilisez le projet Lombok pour générer votre méthode toString(), utilisez @ToString.Exclude pour exclure un champ du résultat de toString().
Soyez également très prudent lorsque vous exposez des données à l’extérieur. Par exemple, si un point de terminaison de votre système affiche tous les noms d’utilisateur, il n’est pas nécessaire de montrer l’identifiant unique interne. Cet identifiant pourrait servir à associer l’utilisateur à des informations plus sensibles via d’autres points de terminaison. Si vous utilisez Jackson pour sérialiser et désérialiser des POJO en JSON, utilisez @JsonIgnore et @JsonIgnoreProperties pour empêcher la sérialisation ou la désérialisation de ces propriétés.
Si vous devez transmettre des données sensibles à d’autres services, chiffrez-les correctement et sécurisez votre connexion avec HTTPS, par exemple.
5. Assainissez toutes les entrées
Le cross-site scripting (XSS) est un problème bien connu, principalement exploité dans les applications JavaScript. Java n’y échappe toutefois pas. Une attaque XSS consiste simplement à injecter du code JavaScript exécuté à distance. Selon OWASP, la règle n° 0 pour prévenir les attaques XSS est la suivante : « N’insérez jamais de données non fiables, sauf dans les emplacements autorisés. » Pour prévenir ce risque de sécurité en Java, la solution de base consiste à éviter autant que possible les données non fiables et à assainir toutes les autres avant de les utiliser. La bibliothèque d’encodage OWASP pour Java constitue un bon point de départ : elle fournit de nombreux encodeurs.
Assainir les saisies de texte des utilisateurs semble évident. Mais qu’en est-il des données que vous récupérez dans une base de données, même si elle vous appartient ? Et si votre base de données avait été compromise et qu’une personne y avait inséré du texte malveillant dans un champ ou un document ?
Surveillez également les fichiers entrants. La vulnérabilité Zip-slip présente dans de nombreuses bibliothèques est due à l’absence d’assainissement du chemin des fichiers compressés. Les archives ZIP contenant des fichiers dont le chemin est ../../../../foo.xy pourraient être extraites et potentiellement écraser des fichiers arbitraires. Il ne s’agit pas d’une attaque XSS, mais c’est un bon exemple de l’importance d’assainir toutes les entrées. Toute entrée peut être malveillante et doit être assainie en conséquence.
6. Configurez vos analyseurs XML pour prévenir les attaques XXE
Lorsque les entités externes XML (XXE) sont activées, il est possible de créer un fichier XML malveillant, comme ci-dessous, et de lire le contenu de n’importe quel fichier sur la machine. Sans surprise, les attaques XXE figurent dans la liste OWASP Top 10 et constituent une vulnérabilité de sécurité en Java à prévenir. Les bibliothèques XML Java sont particulièrement vulnérables aux injections XXE, car la plupart des analyseurs XML activent les entités externes par défaut.
Une implémentation naïve de DefaultHandler et de l’analyseur Java SAX, comme celle présentée ci-dessous, analyse ce fichier XML et révèle le contenu du fichier passwd. L’exemple se concentre sur l’analyseur Java SAX, mais d’autres analyseurs, comme DocumentBuilder et DOM4J, ont un comportement par défaut similaire.
Pour prévenir ce type d’attaque, modifiez les paramètres par défaut afin d’interdire les entités externes et les doctypes dans xerces1 ou xerces2, respectivement.
Pour obtenir des conseils pratiques sur la prévention des injections XXE malveillantes, consultez l’aide-mémoire OWASP sur les XXE. Vous pouvez également regarder ma vidéo sur la prévention des attaques par injection d’entités externes (XXE).

7. Évitez la sérialisation Java
La sérialisation en Java permet de transformer un objet en flux d’octets. Ce flux est soit enregistré sur le disque, soit transmis à un autre système. À l’inverse, un flux d’octets peut être désérialisé afin de recréer l’objet d’origine.
Le principal problème concerne la désérialisation. En général, cela ressemble à ceci :
Il est impossible de savoir ce que vous désérialisez avant de l’avoir décodé. Un attaquant peut sérialiser un objet malveillant et l’envoyer à votre application. Lorsque vous appelez readObject(), les objets malveillants ont déjà été instanciés. Vous pourriez penser que ce type d’attaque est impossible, car elle nécessite la présence d’une classe vulnérable dans votre classpath. Pourtant, si vous tenez compte du nombre de classes présentes dans votre classpath — votre propre code, les bibliothèques Java, les bibliothèques tierces et les frameworks —, il est très probable qu’une classe vulnérable s’y trouve.
La sérialisation Java est aussi surnommée « le cadeau qui ne cesse de faire des heureux » en raison des nombreux problèmes qu’elle a causés au fil des ans. Oracle prévoit de supprimer à terme la sérialisation Java dans le cadre de Project Amber. Cependant, cela pourrait prendre du temps, et il est peu probable que le problème soit résolu dans les versions précédentes. Il est donc préférable d’éviter autant que possible la sérialisation Java. Si vous devez implémenter Serializable dans vos entités de domaine, mieux vaut implémenter votre propre méthode readObject(), comme ci-dessous. Cela empêche la désérialisation.
Si vous devez désérialiser vous-même un flux d’entrée, utilisez un ObjectsInputStream avec des restrictions. Le ValidatingObjectInputStream d’Apache Commons IO en est un bon exemple. Ce ObjectInputStream vérifie si l’objet en cours de désérialisation est autorisé.
Les problèmes de désérialisation ne se limitent pas à la sérialisation Java. La désérialisation du JSON en objets Java peut présenter des problèmes similaires. Vous trouverez un exemple de ce type de problème avec la bibliothèque Jackson dans l’article de blog « Vulnérabilité de désérialisation Jackson ».
Pour en savoir plus, consultez également notre article de blog Sérialisation et désérialisation en Java : comprendre la vulnérabilité de désérialisation Java.
8. Utilisez des algorithmes de chiffrement et de hachage robustes.
Si vous devez stocker des données sensibles dans votre système, vous devez vous assurer qu’elles sont correctement chiffrées. Vous devez d’abord déterminer le type de chiffrement dont vous avez besoin, par exemple symétrique ou asymétrique. Vous devez également choisir le niveau de sécurité requis. Un chiffrement plus robuste prend plus de temps et consomme davantage de ressources CPU. Surtout, vous n’avez pas besoin d’implémenter vous-même les algorithmes de chiffrement. Le chiffrement est complexe, et une bibliothèque fiable s’en charge pour vous.
Si, par exemple, nous voulons chiffrer des données de carte bancaire, nous avons probablement besoin d’un algorithme symétrique, car nous devons pouvoir retrouver le numéro d’origine. Prenons l’Advanced Encryption Standard (AES), qui est actuellement l’algorithme de chiffrement symétrique de référence pour les organismes fédéraux américains. Pour chiffrer et déchiffrer, inutile de plonger dans les subtilités de la cryptographie Java bas niveau. Nous vous recommandons d’utiliser une bibliothèque qui se charge du gros du travail, comme Google Tink.
Voici un court exemple d’utilisation du chiffrement authentifié avec données associées (AEAD) et AES. Cette méthode permet de chiffrer le texte en clair et de fournir des données associées qui seront authentifiées, mais pas chiffrées.
Pour les mots de passe, il est plus sûr d’utiliser un algorithme de hachage cryptographique robuste, car nous n’avons pas besoin de retrouver les mots de passe d’origine, mais simplement de comparer leurs empreintes. Selon l’aide-mémoire OWASP sur les mots de passe, les meilleurs algorithmes de hachage de mots de passe sont actuellement Argon2 et BCrypt. Pour les systèmes existants, Scrypt peut être utilisé dans une certaine mesure. Tous trois sont des fonctions de hachage cryptographiques à sens unique, dont le calcul est complexe et prend beaucoup de temps. C’est exactement ce que vous recherchez, car les attaques par force brute deviennent alors extrêmement longues.
Spring Security prend en charge une grande variété d’algorithmes. Essayez les classes Argon2PasswordEncoder et BCryptPasswordEncoder fournies par Spring Security 5.0 pour le hachage des mots de passe.
Un algorithme de chiffrement robuste aujourd’hui peut devenir faible dès l’année prochaine. Le chiffrement doit donc être réévalué régulièrement afin de vérifier que vous utilisez l’algorithme adapté. Utilisez des bibliothèques de sécurité éprouvées pour ces tâches et maintenez-les à jour.
9. Activez le gestionnaire de sécurité Java
Par défaut, un processus Java n’est soumis à aucune restriction. Il peut accéder à toutes sortes de ressources, comme le système de fichiers, le réseau, les processus externes, etc. Il existe toutefois un mécanisme qui contrôle toutes ces autorisations : le gestionnaire de sécurité Java. Par défaut, il n’est pas activé et la JVM dispose d’un accès illimité à la machine. Même si nous ne voulons probablement pas que la JVM accède à certaines parties du système, elle en a la possibilité. Plus important encore, Java fournit des API capables d’effectuer des opérations nuisibles et imprévues.
L’API Attach est probablement la plus inquiétante. Elle permet de se connecter à d’autres JVM en cours d’exécution et de les contrôler. Par exemple, il est assez facile de modifier le bytecode d’une JVM en cours d’exécution si vous avez accès à la machine. Cet article de blog de Nicolas Frankel montre comment procéder.
L’activation du gestionnaire de sécurité Java est simple. Il suffit de démarrer Java avec un paramètre supplémentaire pour l’activer avec la stratégie par défaut : java -Djava.security.manager.
Toutefois, il est peu probable que la stratégie par défaut réponde parfaitement aux besoins de votre système. Vous devrez peut-être créer votre propre stratégie et la fournir à la JVM : java -Djava.security.manager -Djava.security.policy==/foo/bar/custom.policy
Notez le double signe égal : il remplace la stratégie par défaut. Avec un seul signe égal, votre stratégie personnalisée complète la stratégie par défaut.
Pour en savoir plus sur les autorisations du JDK et la rédaction de fichiers de stratégie, consultez la documentation officielle Java.
Remarque :Depuis la sortie de Java 17, le gestionnaire de sécurité est marqué comme « obsolète » en raison de la mise en œuvre de JEP 415. Il reste néanmoins pleinement fonctionnel dans Java 17. Actuellement, la majorité des développeurs Java utilisent Java 8 ou Java 11 en production. Ainsi, même si le gestionnaire de sécurité sera supprimé à l’avenir, il reste utile en pratique.
10. Centralisez la journalisation et la surveillance
La sécurité ne consiste pas uniquement à prévenir les incidents. Vous devez aussi pouvoir détecter les problèmes pour agir en conséquence. La bibliothèque de journalisation que vous utilisez importe peu. L’essentiel est de consigner un grand nombre d’événements, car une journalisation insuffisante reste un problème majeur selon l’OWASP Top 10. En règle générale, tout événement susceptible de faire l’objet d’un audit doit être consigné. Les exceptions, les connexions et les échecs de connexion sont des exemples évidents, mais vous devriez probablement consigner chaque requête entrante, y compris son origine. Vous saurez ainsi au moins quoi, quand et comment un événement s’est produit en cas de piratage.
Il est conseillé de centraliser la journalisation. Si vous utilisez Log4j ou Logback, il est assez facile de les connecter à une pile Elastic centralisée, par exemple. Avec des outils comme Kibana, tous les journaux de l’ensemble de vos serveurs ou systèmes peuvent être consultés et recherchés pour mener des investigations.
En plus de la journalisation, vous devez surveiller activement vos systèmes et stocker ces valeurs de manière centralisée, pour qu’elles soient facilement accessibles. Des pics de consommation CPU ou une charge énorme provenant d’une seule adresse IP peuvent signaler un problème ou une attaque. Associez la journalisation centralisée à une surveillance en temps réel et à des alertes pour être averti dès qu’un événement inhabituel se produit.
La réinitialisation du mot de passe administrateur, l’accès à un serveur interne depuis une adresse IP externe ou un paramètre d’URL comme ‘UNION’ ne sont que quelques exemples d’indicateurs d’un problème. Si vous recevez des alertes pertinentes et que vous parvenez à retracer ce qui s’est réellement passé, vous avez de grandes chances d’éviter des dégâts plus importants et de corriger la faille à temps.
FAQ
Qu’est-ce que la sécurité Java ?
La sécurité Java désigne les mesures prises par un développeur Java pour empêcher un utilisateur malveillant de compromettre une application. En écrivant du code Java robuste et sécurisé, un développeur protège la confidentialité, l’intégrité et la disponibilité de l’application et de ses données.
Java présente-t-il un risque pour la sécurité ?
Java ne présente pas nécessairement un risque pour la sécurité. En effectuant correctement les mises à niveau de Java, en n’utilisant que les modules nécessaires et en développant vos applications dans une optique de sécurité, vous réduisez les risques. Cet aide-mémoire vous aide à prévenir les vulnérabilités de sécurité Java dans les applications que vous développez.
Où se trouve le fichier de sécurité Java ?
Le fichier java.security se trouve dans l’environnement d’exécution Java (JRE) et contient les propriétés de sécurité par défaut. Avec Java 8 ou une version antérieure, il se trouve dans $JAVA_HOME/jre/lib/security. Dans les versions plus récentes de Java, il se trouve dans $JAVA_HOME/conf/security.
Lancez-vous dans les compétitions Capture The Flag
Apprenez à résoudre des défis Capture The Flag en regardant à la demande notre atelier virtuel d’initiation.



