Skip to main content

Journalisation Java : que faut-il consigner, et que faut-il éviter ?

Écrit par
java loggging feature

11 novembre 2020

0 minutes de lecture

Les journaux sont très utiles pour repérer les erreurs et déboguer le code. Pour les ingénieurs, et plus particulièrement dans un environnement DevOps, ils constituent un outil précieux.

Au-delà de leur rôle fonctionnel, les journaux sont également essentiels du point de vue de la sécurité Java. En cas de faille de sécurité, vos fichiers journaux sont le premier endroit où chercher des indices sur ce qui s’est passé. Mais avec le grand nombre de frameworks de journalisation disponibles pour les applications Java, comment choisir le bon ? Et surtout, comment bien journaliser en Java ?

La qualité des journaux est essentielle. Consigner trop ou trop peu d’informations peut avoir des conséquences catastrophiques du point de vue de la sécurité Java. D’ailleurs, la journalisation et la surveillance insuffisantes (A10:2017) figurent dans le Top 10 de l’OWASP.

Dans cet article, je vous propose une approche pragmatique de la journalisation Java : que faut-il consigner, que faut-il éviter et comment mettre en œuvre correctement la journalisation en Java ?

Que faut-il consigner ?

Quand j’étais dans l’armée, chaque fois que je devais faire un rapport, je devais utiliser la méthode des 5W-H — Who, What, Where, When, Why et How (qui, quoi, où, quand, pourquoi et comment). Cette méthode constitue également une excellente règle pour rédiger les journaux.

Qui a effectué cette action ?

Identifiez l’utilisateur ou le système à l’origine de la requête. Si possible, consignez son identifiant utilisateur et son adresse IP.

Que se passe-t-il ?

De quel type de message de journalisation s’agit-il ? De nombreux développeurs connaissent déjà les différents niveaux de journalisation, qui indiquent l’importance d’un message (ERROR, WARN, INFO, DEBUG, TRACE). Il est judicieux de distinguer la journalisation fonctionnelle, la journalisation de sécurité et, le cas échéant, la journalisation d’audit.

Où cela s’est-il produit ?

Identifiez l’emplacement. En général, vous définissez un logger par classe, ce qui constitue déjà un bon point de départ. Toutefois, vous devez aussi connaître la méthode concernée. Et si vous utilisez une architecture microservices, par exemple, vous devez également savoir de quel nœud il s’agit.

Quand cela s’est-il produit ?

L’heure est essentielle : veillez donc à consigner un horodatage. Tenez compte des différences de fuseau horaire et, surtout, des changements d’heure deux fois par an. Je vous conseille de toujours utiliser l’UTC, afin que l’heure soit claire pour tout le monde.

Pourquoi cela s’est-il produit ?

C’est généralement ce que vous devez découvrir. Vous ne pouvez pas toujours le consigner dans une seule ligne de journal. Dans certains cas, la raison est évidente. Si c’est le cas, intégrez-la au message. Souvent, on ne peut expliquer le « pourquoi » qu’en observant les liens entre les journaux. Veillez donc à rédiger des messages parfaitement clairs.

Comment cela s’est-il produit ?

Comme pour le « pourquoi », le « comment » ne peut généralement pas être consigné dans un seul message. Il est donc extrêmement important de pouvoir établir une corrélation entre les messages, en particulier dans une architecture orientée services. Pour ce faire, créez un identifiant de corrélation unique pour chaque requête entrante dans chaque service disposant d’une interface sortante. Une fois cet identifiant créé, consignez-le dans chaque message. Si vous devez le transmettre à un autre service, vous pouvez utiliser l’en-tête HTTP spécifique X-Correlation-ID pour établir la corrélation tout en préservant l’absence d’état.

Journalisation des événements de sécurité

Java est principalement utilisé pour des processus côté serveur qui interagissent directement avec les données. Nous devons donc suivre les événements liés à la sécurité. Il faut les hiérarchiser en conséquence et accorder une attention particulière aux événements nécessitant des privilèges élevés (événements d’administration).

De manière générale, pensez à consigner les événements suivants :

  • les réussites et les échecs de connexion

  • les déconnexions

  • les modifications et réinitialisations de mots de passe

  • la création et la suppression d’utilisateurs

  • les échecs d’autorisation

  • les changements de niveau d’accès

Vous devez également consigner les indicateurs susceptibles de révéler des attaques spécifiques.

  • les erreurs de validation des entrées : repérez les balises de script pouvant entraîner des attaques XSS, ou les paramètres tels que UNION, qui peuvent indiquer une injection SQL

  • les réponses HTTP 4xx et 5xx

  • les volumes anormalement élevés de requêtes provenant d’une adresse IP spécifique

  • vérifiez les plages d’adresses IP : par exemple, si un service interne reçoit une requête provenant d’une adresse IP externe

Il est judicieux de définir des seuils pour certains événements de sécurité consignés. Vous pouvez déclencher des alertes dès que ces seuils sont dépassés. Vous pourrez ainsi intervenir en conséquence et éviter des dégâts plus importants.

Que ne faut-il pas consigner ?

Consigner trop d’informations est tout aussi nuisible que d’en consigner trop peu. Vos journaux ne doivent pas être saturés d’informations inutiles en production. Veillez donc à toujours définir un niveau de journalisation adapté sur les systèmes de production, comme WARN ou ERROR.

Lors du développement, les ingénieurs consignent parfois des données sensibles susceptibles de contenir des informations personnelles pour déboguer les applications. Même si nous devons chercher à éviter cela, les messages de débogage internes doivent utiliser le niveau de journalisation approprié — par exemple DEBUG ou TRACE — et ne doivent pas être visibles en production.

En règle générale, une application ne doit révéler aucune donnée propre à l’application ou à l’utilisateur : un attaquant pourrait les exploiter. De plus, exposer des informations personnelles risque fort de vous mettre en infraction avec les réglementations sur la protection de la vie privée. Même si seules des personnes internes peuvent consulter les journaux, cela constitue un point de défaillance unique. En bref, ces informations ne doivent pas tomber entre de mauvaises mains.

Pendant le développement, réfléchissez aux informations que vous devez consigner. Faut-il vraiment consigner un identifiant interne, un mot de passe non chiffré ou les coordonnées de carte bancaire d’une personne ? Évitez d’afficher un objet de données complet en Java, car le résultat dépend de la méthode toString() de l’objet concerné.

En consignant des informations personnelles à des fins de débogage, nous contournons bon nombre de nos propres politiques de sécurité. Il serait préférable de ne jamais consigner ce type d’informations. Si vous devez consigner certaines données, veillez à définir le niveau de journalisation approprié, comme DEBUG. Vous devez également définir un niveau supérieur sur le système de production, comme WARN.

En résumé : les informations personnelles et sensibles ne doivent jamais figurer dans les journaux de production.

Journalisation de sécurité en Java

Avant tout, utilisez un framework de journalisation en Java. Cela semble évident, mais dans de nombreuses applications, on utilise encore l’ancienne méthode System.out.println() pour écrire directement dans la console. Il faut absolument éviter cette pratique. Un bon linter (Spotbugs, PMD, SonarLint) peut vous y aider.

Il existe de nombreux frameworks de journalisation pour Java. Cet article se concentre sur Logback et Log4j2, les frameworks les plus utilisés actuellement. Quel que soit le framework, il est toutefois judicieux de séparer la journalisation de sécurité de la journalisation générale de l’application. Avec Logback comme avec Log4j2, vous pouvez facilement le faire en ajoutant des marqueurs au logger. Grâce à un marqueur spécifique, comme SECURITY ou éventuellement AUDIT, vous pouvez facilement filtrer ces messages dans vos journaux. Vous pouvez aussi ajouter un appender supplémentaire à votre logger et filtrer selon le marqueur pour stocker ou afficher ces journaux séparément.

Logback

Pour mettre en œuvre Logback, vous avez uniquement besoin de la dépendance logback-classic, comme indiqué ci-dessous. Elle inclura également les packages logback-core et slf4j-api.

<dependency>
   <groupId>ch.qos.logback</groupId>
   <artifactId>logback-classic</artifactId>
   <version>1.2.3</version>
</dependency>

Marqueurs

Vous pouvez définir les marqueurs avec n’importe quelle chaîne de caractères et les ajouter à votre logger, quel que soit le niveau de journalisation, comme illustré ci-dessous.

private static final Logger logger = LoggerFactory.getLogger(LogbackExample.class);
private static final Marker security = MarkerFactory.getMarker("SECURITY");

public boolean  login(String userName, String encryptedPassword) {
   logger.warn(security, "login attempt by user: {}", userName);
  ...
}

Configuration

Dans l’exemple de configuration Logback ci-dessous, j’ai créé deux appenders dans mon fichier logback.xml.

Le premier appender affiche toutes les lignes de journal dans la console, y compris les marqueurs que j’ai pu ajouter. Le second filtre uniquement les journaux de sécurité à l’aide du marqueur SECURITY et les écrit chaque jour dans un fichier journal de sécurité distinct.

<configuration>
   <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
       <encoder>
           <pattern>%d{HH:mm:ss.SSS} [%thread] %-8marker %-5level %logger{36} - %msg%n</pattern>
       </encoder>
   </appender>

   <timestamp key="byDay" datePattern="yyyyMMdd"/>
   <appender name="SECURITY-FILE" class="ch.qos.logback.core.FileAppender">
       <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
           <evaluator class="ch.qos.logback.classic.boolex.OnMarkerEvaluator">
               <marker>SECURITY</marker>
           </evaluator>
           <OnMatch>ACCEPT</OnMatch>
           <OnMismatch>DENY</OnMismatch>
       </filter>
       <file> security-${byDay}.log</file>
       <append>true</append>
       <encoder>
           <pattern>%d{HH:mm:ss.SSS} [%thread] %-8marker %-5level %logger{36} - %msg%n</pattern>
       </encoder>
   </appender>

   <root level="warn">
       <appender-ref ref="STDOUT" />
       <appender-ref ref="SECURITY-FILE" />
   </root>
</configuration>

Log4j2

Pour l’utiliser, vous devez inclure les packages log4j-api et log4j-core, comme illustré ci-dessous.

<dependency>
   <groupId>org.apache.logging.log4j</groupId>
   <artifactId>log4j-api</artifactId>
   <version>2.13.3</version>
</dependency>

<dependency>
   <groupId>org.apache.logging.log4j</groupId>
   <artifactId>log4j-core</artifactId>
   <version>2.13.3</version>
</dependency>

Marqueurs

La création et l’ajout des marqueurs sont similaires à l’exemple Logback. Cette fois, nous utilisons toutefois le gestionnaire Log4j spécifique pour créer le logger et le marqueur.

private static final Logger logger = LogManager.getLogger(Log4j2Example.class);
private static final Marker security = MarkerManager.getMarker("SECURITY");

public boolean login(String userName, String encryptedPassword) {
   logger.warn(security, "login attempt by user: {}", userName);
  ...
}

Configuration

Comme pour la configuration Logback, j’ai créé une configuration Log4j2 avec deux appenders. Les journaux de sécurité sont stockés dans un fichier distinct selon les marqueurs. Cette configuration doit se trouver dans le fichier log4j2.xml présent dans le classpath.

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
   <Appenders>
       <Console name="Console" target="SYSTEM_OUT">
           <PatternLayout pattern="%d{HH:mm:ss.SSS} [%thread] %-8marker %-5level %logger{36} - %msg%n"/>
       </Console>
       <File name="SECURITY-FILE" fileName="securityFile.log">
           <Filters>
               <MarkerFilter marker="SECURITY" onMatch="ACCEPT" onMismatch="DENY"/>
           </Filters>
           <PatternLayout pattern="%d{HH:mm:ss.SSS} [%thread] %-8marker %-5level %logger{36} - %msg%n"/>
       </File>
   </Appenders>

   <Loggers>
       <Root level="WARN">
           <AppenderRef ref="Console"/>
           <AppenderRef ref="SECURITY-FILE"/>
       </Root>
   </Loggers>
</Configuration>

Journalisation pour les systèmes distribués

Pour les systèmes distribués, il est préférable de ne pas écrire les journaux dans un fichier unique par service. Optez plutôt pour un emplacement centralisé. Le stockage centralisé des journaux facilite la corrélation des lignes de journal et la recherche des problèmes potentiels.

Conseil : lors de la mise en place d’une journalisation centralisée, il est souvent utile de configurer les journaux au format JSON. L’indexation et la recherche seront ainsi plus faciles et plus rapides.

En plus de centraliser la journalisation, pensez à surveiller activement vos journaux et à configurer des alertes. De nombreux produits permettent de générer des graphiques en temps réel à partir de vos journaux et de déclencher des alertes dès que les valeurs dépassent un seuil prédéfini.

Conclusion

La journalisation ne se résume pas à consigner des informations. Il est essentiel d’examiner de près ce que vous consignez, à quel moment vous le faites et la manière dont vous exploitez vos journaux pour mener rapidement des recherches ou intervenir. Mieux vaut prévenir que guérir, mais il reste toujours possible qu’une personne compromette votre système : vous devez alors éviter que les dégâts ne s’aggravent.

Si ce moment difficile devait arriver, assurez-vous que vos journaux puissent vous aider !

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.

Lire la suite

blog feature ai violet
Blog

VulnBump : les agents IA peuvent-ils corriger les dépendances vulnérables sans casser le build ?

VulnBump évalue des agents IA sur 307 mises à niveau de dépendances réelles. Une correction n’est comptabilisée que si la vulnérabilité disparaît et que la compilation et les tests restent au vert.

blog feature snyk container custom base recommendations
Blog

Découvrez Evo Agentic AppSec : une sécurité au rythme du développement de l’IA

Le développement piloté par l’IA dépasse les workflows de sécurité traditionnels. L’AppSec agentique de Snyk s’appuie sur des agents de sécurité pour détecter, corriger et vérifier les vulnérabilités en continu, tandis que les équipes définissent les exigences.

Blog

Les modèles de pointe ont trouvé les vulnérabilités. Seul l’attaquant a trouvé les chaînes d’exploitation.

L’analyse statique a détecté les failles, mais seuls des tests d’attaque en conditions réelles ont prouvé comment elles pouvaient être enchaînées pour provoquer des compromissions. Comparaison d’Evo COS, de Claude Security et de Claude Code Security.