Ghost in the Log4Shell : détecter au plus tôt les failles de son application.

07 novembre 2025

Toute personne qui travaillait dans une DSI aux environs de la mi-décembre 2021 se souvient de ce moment. On se dirigeait tranquillement vers un retour d’un Noël déconfiné quand la nouvelle tombe : toutes nos applications Java sont menacées par une faille monumentale !! L’ANSSI fait un communiqué annonçant la faille CVE-2021-44228, plus connue sous le nom de Log4Shell, qui concerne toutes les applications Java utilisant la librairie de logs Log4J en version antérieure à la 2.17.0.

Comme ça, ça ne parait pas catastrophique, simplement une nouvelle mise à jour de sécurité. Dans les faits, l’annonce d’une faille zero-day critique crée un raz-de-marée dans toutes les DSI. Tout le monde se met en situation de crise pour corriger au plus vite cette faille béante dans nos systèmes. Rarement il nous avait été donné de voir une telle priorisation des sujets de sécurité et de mise à jour des dépendances, et tout le monde en fait sa priorité numéro 1.

Si cette réaction a eu le mérite de traiter les failles dans la majorité des cas, on sait aussi qu’elle a pu être prise dans une confusion ambiante et entraîner des priorisations disproportionnées.

Et si on voyait ensemble comment éviter ce genre de réaction d’urgence en automatisant la détection de dépendances vulnérables et la mise à jour de celles-ci ?

Outiller sa détection

Avant de penser à mettre à jour nos dépendances, on peut s’assurer que celles dont on dispose ne sont pas la cible de vulnérabilités critiques. Pour ce faire, plusieurs outils existent :

  • Dependency-check, un projet Open Source d’abord porté par Jeremy Long, puis repris par l’OWASP (Open Worldwide Application Security Project), une communauté à l’origine de nombreux top 10 des vulnérabilités, pratiques de sécurité et de nombreux outils Open Source autour de la sécurité
  • Checkmarx, outil clé en main permettant une analyse de sécurité “à la Sonar”
  • Trivy, solution Open Source portée par Aquasec, qui fonctionne sur un modèle freemium
  • Snyk, Veracode, Qualys, …

Ici, nous allons avancer avec Trivy, qui a l’avantage d’être gratuit et Open Source. Initialement pensé pour analyser des images Docker (container scanning), il est désormais possible d’analyser des projets JS, Ruby, des packages Nuget, Java, .NET, Python, et des plugins communautaires existent pour ajouter des nouvelles règles de contrôle, notamment pour la faille Spring-4shell.

Expérimenter Trivy

Pour uniformiser l’implémentation, on va passer par l’image Docker, que l’on va d’abord lancer en local. Pour partir sur une situation que tout le monde peut reproduire, lançons Trivy… Sur Trivy :

docker run aquasec/trivy:0.67.2 image aquasec/trivy:0.67.2

Au-delà du rapport qui va nous informer sur l’état de notre version de Trivy, on peut observer quelques éléments qui nous seront utiles pour la suite :

  1. Comme évoqué, on obtient un rapport par type de technologies rencontré (ici, Docker et Golang)
  2. Les logs nous informent sur les différents scanners activés

On retrouve donc un scanner de secrets et deux scanners de vulnérabilités Alpine et Golang

  1. Trivy a tiré une base de données distante de vulnérabilités. Certaines options permettent de désactiver cette mise à jour et de s’appuyer sur une base locale, ce qui peut être utile pour alléger et accélérer son exécution.

Trivy active donc un certain nombre de scanners par défaut : ici celui pour les secrets, et un pour chaque technologie détectée. On peut également en ajouter d’autres, par exemple, un scanning des bonnes pratiques Docker pour notre image, avec l’option --image-config-scanners misconfig. Si on relance notre analyse avec cette nouvelle option, on obtient un nouveau rapport :

Automatisation

Maintenant qu’on a pu expérimenter notre outil en local, on va l’ajouter dans notre pipeline de CI/CD. Ici, on va l’ajouter à un pipeline Gitlab CI, mais la documentation regorge d’exemples d’implémentations pour d’autres solutions :

# job caché à mettre en template ou en librairie
.trivy:
  stage: build
  image:
    name: aquasec/trivy:latest
    entrypoint: [ "" ]
  variables:
    GIT_STRATEGY: none
    # valeurs à remplacer par votre configuration de registry
    TRIVY_USERNAME: "$CI_REGISTRY_USER"
    TRIVY_PASSWORD: "$CI_REGISTRY_PASSWORD"
    TRIVY_AUTH_URL: "$CI_REGISTRY"
    # nom et tag de l'image à analyser
    FULL_IMAGE_NAME: "A REDEFINIR PAR L'APPELANT"
    # suffixe du rapport codequality à exporter
    REPORT_NAME: "A REDEFINIR PAR L'APPELANT"
  script:
    - trivy --version
    # on nettoie le cache quand on analyse des images qui ont le même tag, sans pour autant supprimer la base de données de vulnérabilités
    - trivy clean --scan-cache
    # mise à jour de la base de vulnérabilités et mise en cache
    - trivy image --download-db-only --no-progress --cache-dir .trivycache/
    # on écrit le rapport dans le répertoire $CI_PROJECT_DIR, pour que `artifacts:` le récupère, et on l’exporte selon le template gitlab-codequality
    - trivy image --exit-code 0 --cache-dir .trivycache/ --no-progress --format template --template "@/contrib/gitlab-codequality.tpl"
      --output "$CI_PROJECT_DIR/gl-code-quality-report-${REPORT_NAME}.json" --timeout 15m "$FULL_IMAGE_NAME"
    # sortie en erreur si on retrouve au moins une faille de criticité CRITIQUE
    - trivy image --exit-code 1 --severity CRITICAL --timeout 15m "$FULL_IMAGE_NAME"
  cache:
    paths:
      - .trivycache/
  artifacts:
    paths:
      - $CI_PROJECT_DIR/gl-code-quality-report-${REPORT_NAME}.json
    reports:
      codequality: $CI_PROJECT_DIR/gl-code-quality-report-${REPORT_NAME}.json
  allow_failure: true

# implémentation du job
"🐋analyse-trivy":
  extends: .trivy
  variables:
    GIT_STRATEGY: none
    # valeurs à remplacer par votre configuration de registry
    TRIVY_USERNAME: "$CI_REGISTRY_USER"
    TRIVY_PASSWORD: "$CI_REGISTRY_PASSWORD"
    TRIVY_AUTH_URL: "$CI_REGISTRY"
    # nom et tag de l'image à analyser
    FULL_IMAGE_NAME: "aquasec/trivy:0.59.0"
    # suffixe du rapport codequality à exporter
    REPORT_NAME: "trivy"

Une nouvelle fonctionnalité que l’on n’a pas exploré avant est l’export d’un rapport dans un autre format que celui de la console. Il est possible d’en générer sous plusieurs formats, comme junit, html, json, et certains modules communautaires permettent d’en avoir d’autres, comme SonarQube, Streamlit ou CycloneDX. 

Ici, on exporte un rapport JSON selon le template code-quality de Gitlab qui va nous permettre d’avoir un onglet dédié aux résultats dans notre pipeline Gitlab CI (en version Premium et Ultimate).

On sait maintenant comment détecter nos failles de sécurité, et notamment quelles dépendances ou versions sont impactées. Super ! Mais du coup, comment on met à jour ? On va pas faire TOUT ÇA à la main quand même ?!

Mettre à jour ses dépendances

Et oui, en bon DevOps, on est fainéants efficients : on optimise les petites choses répétitives pour ne plus avoir à s’en occuper par la suite, surtout quand la machine sait très bien le faire à notre place. Et quoi de mieux que de combiner ça à de l’intelligence collective ?

C’est le principe de Renovate. Si vous ne connaissez pas, pas de panique, on va regarder tout ça en détail !

L’idée de Renovate, c’est d’avoir un bot sur notre projet git, qui va venir scanner nos dépendances régulièrement, et identifier si, à tout hasard, une version plus récente ne serait pas sortie. Et à chaque fois qu’il va trouver une version plus récente d’une de nos dépendances, il va créer une Merge Request pour proposer une montée de version.

Pour le mettre en place, l’option la plus simple est d’automatiser son déclenchement, via le projet Renovate Runner, qui permet d’instancier un type de runner renovate sur votre instance Gitlab. Il est ensuite possible de le programmer pour qu’il s’exécute régulièrement, et vienne ainsi se lancer sur l’ensemble de votre instance automatiquement.

Ça c’est pour les grandes lignes du service. Si on détaille un peu plus, on va pouvoir le configurer de manière à ignorer certaines dépendances (pratique si on sait qu’on a absolument besoin d’une version spécifique), en grouper d’autres pour les montées de version de façon coordonnée, ou encore lui dire de ne surtout pas faire de montée de version majeure en autonomie. Enfin, on peut lui donner des conditions “d’automerge”, afin qu’au fil de l’eau, ces MR soient analysées, traitées et mergées, sans intervention humaine.

Cette configuration, elle se fait dans un fichier renovate.json qu’on vient placer à la racine du projet à maintenir. Un exemple de configuration basique peut ressembler à ça :

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  # contient plusieurs configuration recommandées par défaut
  "extends": [
    "config:best-practices"
  ],
  "baseBranches": [
    "develop"
  ],
  "rebaseWhen": "auto",
  "labels": [
    "Renovate",
  ],
  # planification du runner renovate
  "schedule": [
    "after 8am and before 8pm every weekday"
  ],
  # automerge automatique des MR Renovate
  "automergeSchedule": [
    "after 8am and before 8pm every weekday"
  ],
  "updateNotScheduled": false,
  # autorise à merger sans avoir eu de review humaine
  "gitLabIgnoreApprovals": true,
  "packageRules": [
    {
      "automerge": true,
      "matchUpdateTypes": [
        "minor",
        "patch",
        "pin",
        "digest"
      ],
      "major": {
        "automerge": false
      },
      "minor": {
        "automerge": false
      },
      "patch": {
        "automerge": false
      },
      "pin": {
        "automerge": false
      },
      "digest": {
        "automerge": false
      },
      "minimumReleaseAge": "7 days"
    }
  ],
  "pinDigests": false,
  # ajoute la possibilité d'obtenir des MR pour des fix de vulnérabilités
  "osvVulnerabilityAlerts": true
}

L’intelligence collective au service de nos mises à jour

Bon super, il peut fonctionner seul, proposer des Merge Requests de façon autonome et donc automatiser mes montées de version. Mais elle est où mon intelligence collective là ??

Laissez-moi vous parler de la Merge Confidence. Si vous êtes comme moi, vous ne sautez pas sur la dernière nouveauté. Vous attendez que les autres testent, voient si ça tient la route, et alors peut-être que vous envisagez de faire le grand saut. La Merge Confidence, c’est exactement ça.

Renovate va compiler différentes métriques extraites des Pull Requests de leur agent GitHub, comme la date de sortie de la nouvelle version, le taux d’adoption de cette version par les projets utilisant cette dépendance (parmi les utilisateurs de Renovate) et le nombre de montée de version qui se sont bien passées, et il va générer un niveau de confiance associé. Ce niveau peut prendre 4 valeurs :

  • Low : concrètement, ça va casser
  • Neutral : Renovate n’a pas assez d’informations pour décider de la valeur de cette mise à jour
  • High : Renovate estime que le recul est assez bon pour faire la montée de version
  • Very High : la version est éprouvée et les montées de versions se sont très bien passées, on peut y aller (presque) les yeux fermés

Ce niveau, on va pouvoir l’utiliser dans notre configuration et dire à Renovate de ne merger automatiquement, par exemple, que les montées de version possédant un niveau de confiance High ou Very High, pour être plus confiants.

{ 
   "packageRules": [
     {
      	"groupName": "Mend: high confidence minor and patch dependency updates",
      	"matchUpdateTypes": ["minor", "patch"],
      	"matchConfidence": ["very high", "high"],
        "automerge": true
     }
   ]
}

Une fois cette configuration mise en place, toute nouvelle MR proposant de faire une montée de version aura un récapitulatif dans sa description, répertoriant ces informations de confiance.

Cette option nécessitant une clé d’API payante, il n’est pas toujours possible de la mettre en place. Une alternative est de configurer différents paramètres permettant de différer les montées de version, notamment le “minimumReleaseAge” qui peut-être configuré en jours. Renovate recommande d’ailleurs d’activer ce paramètre à 14 jours par défaut.

Grouper les mises à jour

Maintenant que nos dépendances se mettent à jour automatiquement, va se poser un dernier problème : les breaking changes ou changements cassants. La hantise de beaucoup de mainteneurs de projets et la crainte principale à faire une montée de version, surtout quand certaines dépendances sont employées par plusieurs autres.

Pour palier ce problème, on peut créer des groupes de merge, toujours dans notre fichier de configuration Renovate. Ici, l’idée est de définir des règles de regroupement des mises à jours, que ce soit en fonction du package visé ou du type de montée de version (patch, mineure, majeure). Par exemple, pour créer un groupe pour toutes les montées de version non-majeures, on peut ajouter la configuration suivante :

{
  "packageRules": [
    {
      "groupName": "all non-major dependencies",
      "groupSlug": "all-minor-patch",
      "matchPackageNames": [
        "*"
      ],
      "matchUpdateTypes": [
        "minor",
        "patch"
      ]
    }
  ]
}

Et voilà ! Avec tout ça, on est parés ! On détecte au plus tôt nos failles avec Trivy et avec Renovate, nos dépendances mineures sont mises à jour automatiquement, tandis que nos montées de versions majeures disposent d’une note de confiance et l’on peut prendre la décision de la faire ou non, aussi en fonction de la présence ou non de failles de sécurité dans notre version actuelle !

Références

ANSSI : https://www.cert.ssi.gouv.fr/alerte/CERTFR-2021-ALE-022/

Log4Shell : https://www.kaspersky.fr/blog/log4shell-still-active-2022/19881/

Trivy : https://trivy.dev/latest/

Dependency-check : https://github.com/dependency-check/DependencyCheck

OWASP : https://owasp.org/www-project-top-ten/

Checkmarx : https://checkmarx.com/

Renovate : https://docs.renovatebot.com/configuration-options/

Renovate pour Gitlab : https://docs.renovatebot.com/examples/self-hosting/#gitlab-cicd-pipeline 

Mend Renovate : https://docs.mend.io/wsk/renovate-smart-merge-control-implementation-exampl

Kendall Forest, DevOps.

Valeuriad
Par Valeuriad
07 novembre 2025
Nos derniers articles