Aller au contenu principal

Software Supply Chain Failures (défaillances de la chaîne d'approvisionnement logicielle)

Schéma

  • Comprendre qu'un site peut rester vulnérable même si son propre code n'a jamais changé, à cause des bibliothèques tierces (dépendances) qu'il utilise (Software Supply Chain Failures, n°3 du Top 10 de l'OWASP (2025)).
  • Apprendre à utiliser composer audit pour détecter des failles de sécurité déjà publiées dans les dépendances d'un projet PHP.
attention

Un projet PHP moderne n'est presque jamais composé uniquement de code écrit par son équipe : il s'appuie sur des dizaines de bibliothèques externes, elles-mêmes construites sur d'autres bibliothèques. Une seule de ces briques, oubliée et jamais mise à jour, suffit à rendre tout l'édifice vulnérable, sans qu'une seule ligne du code du site n'ait jamais changé.


1. Création du projet vulnérable​

Créez un dossier tp-dependances contenant un fichier composer.json, qui installe une ancienne version d'une bibliothèque très utilisée, guzzlehttp/guzzle (un client HTTP, qui permettrait au site du club de randonnée d'interroger un service météo externe, par exemple) :

{
"require": {
"guzzlehttp/guzzle": "6.3.0"
},
"config": {
"audit": {
"block-insecure": false
}
}
}

Installez cette dépendance :

composer install
info

L'option audit.block-insecure à false est nécessaire ici uniquement pour les besoins de ce TP : les versions récentes de Composer refusent désormais, par défaut, d'installer une dépendance déjà connue comme vulnérable. Vous venez de découvrir, avant même la première ligne de ce TP, une protection déjà intégrée à votre outillage ! Nous la désactivons volontairement, le temps d'observer la faille, exactement comme le ferait un projet démarré avant l'existence de cette protection, ou sur une version plus ancienne de Composer.


2. Observer ce qui a été réellement installé​

Affichez la liste des paquets installés par Composer :

composer show

Question : Le fichier composer.json ne mentionne qu'une seule bibliothèque, guzzlehttp/guzzle. Pourquoi composer show en affiche-t-il cinq ?

📌 Une réponse

attention

La chaîne d'approvisionnement logicielle, c'est exactement cela : une suite de maillons, où chaque bibliothèque dépend d'autres bibliothèques, parfois sur plusieurs niveaux. Il suffit qu'un seul maillon, même indirect, soit compromis ou obsolète pour que l'ensemble du projet soit concerné.


3. Détecter les failles connues avec composer audit​

Composer intègre un outil qui compare les versions installées à une base de données de failles déjà publiées. Lancez-le :

composer audit

Résultat attendu (au moment de la rédaction de cette séance) : Composer affiche une liste de 18 alertes de sécurité, réparties entre guzzlehttp/guzzle (14 alertes) et guzzlehttp/psr7 (4 alertes), avec pour chacune sa gravité, son identifiant CVE et un lien vers le détail de la faille.

info

Ce nombre peut varier dans le temps : de nouvelles failles sont régulièrement découvertes et publiées, y compris pour des versions anciennes, longtemps après leur sortie. C'est précisément pour cela qu'un audit ponctuel ne suffit jamais : un projet non audité depuis plusieurs mois peut être devenu vulnérable entre-temps, sans qu'aucune de ses lignes de code n'ait changé.

Question : Parmi les 18 alertes trouvées, certaines concernent guzzlehttp/psr7, une dépendance que le développeur n'a jamais demandée directement. Pourquoi est-ce quand même son problème ?

📌 Une réponse


4. Mettre à jour la dépendance vulnérable​

Modifiez composer.json pour demander une version récente et maintenue de la bibliothèque :

{
"require": {
"guzzlehttp/guzzle": "^7.0"
}
}

Mettez à jour, puis relancez l'audit :

composer update
composer audit

Résultat attendu : composer audit affiche désormais No security vulnerability advisories found. : plus aucune faille connue, ni sur guzzlehttp/guzzle, ni sur ses dépendances (qui ont, elles aussi, été mises à jour automatiquement).

Bonne pratique - Mettre à jour, pas seulement installer

L'installation d'une dépendance n'est que le début : sans processus de mise à jour régulière, une bibliothèque installée aujourd'hui en toute sécurité peut devenir vulnérable dans six mois, sans que personne ne s'en aperçoive si l'audit n'est jamais relancé.


5. Vérifier la provenance d'une dépendance avant de l'installer​

Avant d'ajouter une nouvelle dépendance à un projet, il est utile de vérifier qu'elle provient réellement d'une source fiable. Ouvrez la page de guzzlehttp/guzzle sur packagist.org, le dépôt officiel des paquets PHP, et notez :

  • son nombre de téléchargements (plusieurs centaines de millions) ;
  • la présence d'un lien vers un dépôt de code source public (GitHub) ;
  • la date de sa dernière mise à jour.

Question : Un attaquant publie un paquet nommé guzzlehtp/guzzle (notez l'orthographe légèrement différente), qui contient un code malveillant caché dans une bibliothèque par ailleurs identique à l'originale. Comment appelle-t-on ce procédé, et à quoi les critères ci-dessus auraient-ils servi pour s'en protéger ?

📌 Une réponse

attention

Contrairement à une dépendance obsolète (ce TP, étapes 1 à 4), qui peut être corrigée après coup par une simple mise à jour, un paquet malveillant installé par erreur a déjà pu exécuter du code sur votre machine ou votre serveur dès son installation (via les scripts automatiques que Composer peut exécuter). La vigilance, ici, doit intervenir avant l'installation.


6. Ajouter une couche de défense supplémentaire : automatiser l'audit​

Se souvenir de lancer composer audit à la main, de temps en temps, n'est pas une protection fiable : ajoutez-le plutôt directement dans votre liste de tâches automatiques, par exemple un script verifier.sh lancé avant chaque mise en production :

#!/bin/bash
echo "Vérification des dépendances du projet..."
composer audit || exit 1
echo "Aucune faille connue détectée."

L'option || exit 1 est essentielle : elle arrête le script (et donc, dans un vrai pipeline, le déploiement) si composer audit détecte la moindre faille.

Bonne pratique - Défense en profondeur

Un audit automatisé à chaque déploiement ne remplace pas une veille régulière sur les failles de sécurité publiées (bulletins de sécurité, abonnement aux alertes GitHub) : il garantit seulement qu'aucune version déjà connue comme vulnérable ne peut être mise en production par erreur, à un moment précis. Les deux mesures se complètent, comme vu dans les séances précédentes de ce cours.


Notions théoriques​

Les Software Supply Chain Failures (« défaillances de la chaîne d'approvisionnement logicielle ») regroupent les failles qui proviennent non pas du code écrit par l'équipe de développement, mais des composants tiers utilisés pour construire, distribuer ou faire fonctionner l'application : bibliothèques, outils de développement, serveurs, systèmes d'exploitation, et même le pipeline qui déploie le site.

remarque

Pour l'édition 2025 de son Top 10, l'OWASP indique que cette catégorie a reçu le plus grand nombre de votes en première place lors de sa consultation de la communauté : la moitié des personnes interrogées la classaient comme le risque n°1.

Les principales formes de défaillance de la chaîne d'approvisionnement​

  • Dépendances obsolètes ou non maintenues, dont les failles de sécurité ont été publiées mais jamais corrigées par une mise à jour (ce TP).
  • Dépendances transitives non surveillées : une bibliothèque sûre peut en installer une autre, bien moins sûre, sans que personne ne l'ait explicitement choisie.
  • Composants provenant de sources non fiables : un paquet au nom proche d'un paquet légitime (typosquatting), ou téléchargé en dehors des dépôts officiels.
  • Absence d'inventaire des composants utilisés, qui empêche de savoir rapidement si le site est concerné lorsqu'une nouvelle faille est publiée sur une bibliothèque donnée.
  • Pipeline de déploiement (CI/CD) moins sécurisé que l'application elle-même, qui en devient le maillon faible : voir la séance sur la sécurisation d'un pipeline CI/CD.
  • Absence de séparation des rôles, lorsqu'une seule personne peut écrire du code et le mettre en production sans aucune relecture.

Pourquoi cette catégorie est-elle si souvent sous-estimée ?​

Une dépendance installée une seule fois, au début d'un projet, continue de fonctionner des années durant sans jamais provoquer d'erreur visible : rien, à l'usage, ne distingue une bibliothèque à jour d'une bibliothèque vulnérable depuis longtemps. C'est tout le contraire d'une injection SQL ou d'un XSS, qui se manifestent par un comportement anormal du site : une dépendance obsolète reste silencieuse, jusqu'au jour où quelqu'un l'exploite.

Comment se protéger des défaillances de la chaîne d'approvisionnement ?​

  • Tenir un inventaire des composants utilisés (parfois appelé SBOM, Software Bill of Materials, la « liste des ingrédients » du logiciel), y compris les dépendances transitives.
  • Auditer régulièrement les dépendances avec des outils comme composer audit (PHP), npm audit (JavaScript), ou des outils dédiés comme OWASP Dependency-Check.
  • Mettre à jour rapidement une dépendance dès qu'une faille connue la concerne, plutôt que d'attendre une maintenance planifiée plusieurs mois plus tard.
  • N'installer des composants que depuis des dépôts officiels (Packagist pour PHP, npm pour JavaScript), en vérifiant leur popularité et leur historique avant un premier usage.
  • Supprimer les dépendances inutilisées, qui élargissent inutilement la surface d'attaque du projet.
  • Sécuriser le pipeline de déploiement autant que l'application elle-même, et prévoir qu'aucune modification ne parte en production sans relecture par une autre personne.
attention

Un site protégé contre l'injection SQL, le XSS, le CSRF et le contrôle d'accès défaillant, mais qui exécute une bibliothèque tierce dont une faille critique est publiée depuis plusieurs mois, reste entièrement vulnérable : la sécurité d'une application ne se limite jamais à son propre code, elle englobe tout ce qu'elle exécute.


Test de mémorisation/compréhension​


Que désignent les Software Supply Chain Failures, au sens de l'OWASP ?


Dans le TP, que fait exactement la commande composer install, exécutée à l'étape 1 ?


Qu'est-ce qu'une dépendance transitive, comme guzzlehttp/psr7 dans ce TP ?


Pourquoi l'option audit.block-insecure a-t-elle dû être désactivée pour réaliser l'étape 1 du TP ?


Que révèle composer audit à l'étape 3 du TP ?


Pourquoi les alertes concernant guzzlehttp/psr7 concernent-elles quand même le développeur du site ?


Que montre le résultat de composer audit après la mise à jour réalisée à l'étape 4 ?


Qu'appelle-t-on le typosquatting, évoqué à l'étape 5 du TP ?


Quels critères permettent de repérer qu'un paquet n'est probablement pas fiable, sur Packagist ?


Pourquoi un paquet malveillant installé par erreur est-il plus dangereux, à certains égards, qu'une dépendance obsolète ?


Pourquoi un audit automatisé à chaque déploiement (étape 6) est-il préférable à un audit fait à la main ?


Pourquoi dit-on qu'une dépendance obsolète est une faille particulièrement « silencieuse » ?



Bonnes pratiques​

  • Lancer composer audit régulièrement, et l'intégrer dans le processus de déploiement.
  • Mettre à jour les dépendances rapidement dès qu'une faille connue est publiée, sans attendre une maintenance planifiée.
  • Tenir un inventaire des composants utilisés, y compris les dépendances transitives.
  • N'installer des paquets que depuis des dépôts officiels (Packagist, npm), après avoir vérifié leur popularité et leur historique.
  • Supprimer les dépendances inutilisées, pour réduire la surface d'attaque du projet.
  • Sécuriser le pipeline de déploiement au même niveau que l'application elle-même.
attention

composer audit ne détecte que les failles déjà publiées et répertoriées. Il ne protège ni contre une faille non encore découverte, ni contre un paquet malveillant publié sous un nom trompeur mais jamais encore signalé : la vigilance lors du choix d'une dépendance reste irremplaçable.


Ce que fait Symfony​

Symfony maintient lui-même ses composants à jour et publie des bulletins de sécurité officiels (symfony.com/security) pour chaque faille découverte dans le framework. La commande composer audit, utilisée dans ce TP, fonctionne exactement de la même façon pour un projet Symfony : elle vérifie l'ensemble des composants Symfony installés au même titre que n'importe quelle autre dépendance.

# Dans un projet Symfony, la même commande s'applique
composer audit
attention

Un projet Symfony reste exposé aux failles de ses dépendances non-Symfony (bibliothèques tierces ajoutées par l'équipe, comme ce TP l'a montré avec Guzzle), même lorsque le cœur du framework est parfaitement à jour. La sécurité du framework ne couvre jamais, à elle seule, l'ensemble de la chaîne d'approvisionnement du projet.