72 CVE dans vos dépendances PHP : comment savoir si vous êtes exposé

72 failles dans un seul composer.lock gelé depuis 2021. Comment lire la sortie de composer audit et trier ce qui vous expose vraiment - cas réel, chiffres réels.

Icônes d'alertes CVE sur terminal legacy dans une salle serveur

Ce composer.lock n'a pas bougé depuis l'été 2021.

Quand je lance composer audit dessus (audit d'un projet PHP/Laravel en production, avril 2026, presque cinq ans plus tard), l'outil remonte 72 failles de sécurité connues, réparties sur 19 paquets. Douze critiques. Trente high. La plus ancienne est documentée depuis le 1er janvier 2020. Elle était là avant le gel. Elle est encore là aujourd'hui.

72, ça fait peur. C'est fait pour.

Mais 72 CVE, ce ne sont pas 72 bugs. Ce sont 72 décisions reportées. Chaque ligne du rapport correspond à un moment où quelqu'un aurait pu lancer composer update, et ne l'a pas fait.

La différence avec une dette technique ordinaire, c'est celle-ci :

Une dette technique, vous la remboursez à votre rythme. Une faille de sécurité, c'est la seule dette où c'est le créancier qui fixe l'échéance. Le créancier, c'est le premier qui scanne votre stack.

Ce qui est intéressant dans ce rapport, c'est pas le total. C'est ce que les dates racontent. On va lire ce composer.lock comme une biographie.

Le projet sur la table

Un projet PHP/Laravel mature. ~44 000 lignes de code PHP. En production depuis des années, application métier critique. Stack : Laravel 6, PHP 7.3. Une stack qui n'est plus suivie, sur laquelle un composer update n'est plus anodin.

Le composer.lock de ce projet n'a pas été touché depuis l'été 2021. Pas de Dependabot, pas de routine de mise à jour, pas d'audit automatique en CI. La machine tournait, les dépendances dormaient.

composer audit est intégré à Composer depuis la version 2.4 : il lit votre composer.lock et le confronte à la base des advisories de sécurité PHP. Aucune installation supplémentaire. Une commande, un rapport.

Voilà ce qu'elle a sorti. J'avais décortiqué ce même projet globalement ici, si vous voulez le contexte d'ensemble avant de plonger dans les dépendances.

Acte 1 : ce qu'ils voyaient et ont laissé

Avant même le gel de l'été 2021, certaines CVE de ce lock étaient déjà connues, documentées, publiques.

sabberworm/php-css-parser : injection de code, publiée le 1er janvier 2020. Niveau critique. Elle était dans le lock depuis avant le gel, et toujours là au moment de l'audit, six ans et quelques mois plus tard.

facade/ignition : c'est la CVE-2021-3129. Exécution de code à distance, CVSS 9.8. Publiée fin 2020. Elle figurait déjà dans le catalogue CISA des failles activement exploitées, des PoC publics circulaient. L'information était disponible. On garde les détails techniques pour la section suivante. Retenez juste qu'elle était bruyante à l'époque.

league/flysystem : RCE aussi, publiée juin 2021, juste avant que le lock gèle.

Trois failles critiques, toutes publiées pendant que l'équipe touchait encore le dépôt. L'information ne manquait pas. Ce qui s'est passé à chaque fois, c'est la même chose : le risque a été vu, et la mise à jour a attendu. "On verra plus tard." C'est une décision, même quand elle n'a jamais été formulée comme telle.

C'est la première moitié de l'histoire : des risques qu'on a regardés en face, et qu'on a laissés passer. La seconde moitié est pire. Parce que là, plus personne ne regardait.

Acte 2 : ce que plus personne ne voyait

À l'été 2021, le dernier commit sur le composer.lock est passé. Après ça, rien.

Un lock figé, c'est un fossile daté. Les dépendances sont celles d'un instant précis. Mais les CVE, elles, continuent d'arriver. Les chercheurs en sécurité continuent de publier. Les packages continuent d'accumuler des advisories, y compris pour les versions figées dans votre lock.

phpoffice/phpspreadsheet porte 20 des 72 CVE à lui seul, presque un tiers du total. Quasi toutes publiées entre 2024 et 2025, trois ans après le gel. Ce seul paquet, abandonné sur place dans le lock depuis 2021, avait concentré presque le tiers de l'exposition totale pendant que le projet continuait de tourner.

Ce n'est pas un trou dans le mur qu'on aurait oublié de boucher. C'est une maison qu'on a quittée en laissant la porte ouverte, et le quartier a changé depuis.

Le code, lui, n'a pas changé après l'été 2021. C'est le monde extérieur qui a continué d'avancer : des chercheurs ont trouvé et documenté des vulnérabilités dans des versions que ce projet utilise encore. Les CVE arrivent de l'extérieur, en continu. Ne pas les surveiller ne les empêche pas d'exister.

72, ce n'est donc pas un score de négligence. C'est une mesure du temps écoulé depuis qu'on a cessé de regarder. Reste une question : sur ces 72, combien vous mettent réellement en danger ? Parce que toutes ne se valent pas.

72, ça ne veut pas dire 72 fois en danger

72 failles dans un lock, ça sonne comme une très mauvaise nouvelle à présenter en CODIR. Ce n'est pas forcément ça, mais il faut savoir trier.

Quand je lis un rapport composer audit, je passe par quatre questions.

1. La gravité brute. 12 critiques, 30 high sur ce projet. Le tri commence par là. Mais un niveau CVSS n'est pas une décision d'action : c'est un point de départ.

2. L'exploitabilité dans votre contexte. C'est la question que l'outil ne pose pas. Et c'est souvent celle qui change tout.

Illustration : facade/ignition, CVE-2021-3129. CVSS 9.8. Dans le catalogue CISA des failles activement exploitées. Des botnets qui scannent en masse. Terrifiant sur le papier.

Sauf que cette faille ne se déclenche que si l'application tourne en debug mode en production (via l'endpoint _ignition/execute-solution). Le debug est coupé en prod sur ce projet. Le 9.8 ne le concerne pas.

La gravité, c'est la note de la faille. L'exposition, c'est ce qu'elle vaut chez vous. Les deux ne sont pas le même chiffre. (C'est vrai dans les deux sens : un 9.8 conditionnel peut ne pas vous toucher, et un 6.5 sur un endpoint exposé peut être votre problème le plus urgent.)

3. Dépendance de prod ou de dev ? Plusieurs des 72 CVE touchent des outils qui ne tournent jamais en production : phpunit, psysh. Le risque existe en théorie, mais il est à requalifier.

4. Patchable ou orpheline ? Certaines se corrigent avec un composer update sans franchir de version majeure : c'est le cas idéal. D'autres vivent dans des paquets abandonnés, ce projet en compte 2. Pas de patch disponible : il faut soit remplacer le paquet, soit contourner la vulnérabilité.

Après ce tri sur ce projet : la pile a rétréci, mais il reste largement de quoi s'inquiéter. Ce qui compte, c'est qu'on sait quoi, et dans quel ordre.

Ce qu'on fait avec ça

Le tri fait, trois chemins s'ouvrent.

Patch immédiat. Tout ce qui se corrige sans casser : composer update sans franchir de version majeure, tests de non-régression, déploiement. C'est le moins cher, à faire en premier. Ces CVE ont une solution qui ne coûte rien à part du temps de recette.

Contournement. Pour ce qui n'est pas patchable immédiatement : une version majeure à franchir, un paquet abandonné sans successeur direct. On coupe le debug en prod, on désactive une feature exposée, on filtre en amont. C'est une mesure temporaire, assumée clairement. Un contournement documenté dans le backlog vaut mieux qu'une faille qu'on fait semblant d'ignorer.

Audit complet. Quand le lock gelé n'est pas qu'un problème de dépendances : c'est un symptôme. Un composer.lock figé depuis 5 ans, ça veut souvent dire qu'on ne peut plus mettre à jour sans tout casser : les versions sont trop distantes, les breaking changes se sont accumulés, les dépendances croisées sont devenues incompatibles. Dans ce cas, les 72 CVE sont le voyant sur le tableau de bord, pas la panne. La panne, c'est l'architecture.

J'ai chiffré ce que ça coûte de laisser traîner ce genre de situation poste par poste. Le risque RGPD n'est qu'un des items.

Ce que 72 CVE racontent vraiment

Lu à plat, ce rapport, c'est 72 problèmes. Lu dans le temps, c'est une seule histoire : celle d'un projet qui a arrêté de se mettre à jour un jour de 2021, et qui a continué de tourner comme si de rien n'était.

Le composer.lock est le document le plus honnête du dépôt. Il date au jour près le moment où l'équipe a cessé de surveiller les dépendances. Aucun rapport de sprint, aucune réunion de backlog ne dira ça aussi clairement.

Ce n'est pas une faute morale. C'est ce qui arrive à la plupart des projets qui vivent assez longtemps : la maintenance s'arrête avant le projet.

Le seul antidote, c'est de ne jamais laisser le lock geler. composer audit en pre-push, et l'écart ne dépasse jamais quelques semaines : voilà comment câbler ça concrètement.