Pourquoi tout le monde devrait utiliser un cooldown des dépendances
(blog.yossarian.net)- Le cooldown des dépendances (dependency cooldown) est une mesure de sécurité simple et efficace qui peut atténuer la plupart des attaques de la chaîne d’approvisionnement open source
- Les attaquants compromettent généralement des projets open source populaires pour diffuser du code malveillant, mais, dans la majorité des cas, la fenêtre d’exposition des attaques est courte, souvent inférieure à une semaine
- En configurant un cooldown qui impose une attente après la publication d’une nouvelle version (par exemple 7 jours), on peut réduire fortement le risque d’infection lié aux mises à jour automatiques
- Dependabot, Renovate, pnpm, etc. prennent déjà en charge nativement cette fonctionnalité de cooldown, avec une configuration simple et sans coût supplémentaire
- Si ce mécanisme est fourni par défaut au niveau des gestionnaires de paquets, il peut contribuer à renforcer la sécurité de la chaîne d’approvisionnement tout en réduisant les alertes inutiles
Structure et problèmes des attaques de la chaîne d’approvisionnement
- La plupart des attaques de la chaîne d’approvisionnement (supply chain attack) suivent le même schéma
- l’attaquant obtient un accès en exploitant le vol d’identifiants d’un projet open source populaire ou des vulnérabilités CI/CD
- il téléverse des modifications malveillantes sur des canaux de distribution (PyPI, npm, etc.)
- les utilisateurs installent la version infectée à cause des mises à jour automatiques ou d’un verrouillage de version insuffisant
- un fournisseur de sécurité détecte le problème et émet une alerte, puis le registre de paquets supprime la version concernée
- L’intervalle entre les étapes (1) et (2) est long, mais les étapes (2) à (5) sont gérées en quelques heures à quelques jours, ce qui laisse une courte période d’activité aux attaquants
- Fenêtre d’opportunité observée sur des cas majeurs des 18 derniers mois
- xz-utils : environ 5 semaines
- Ultralytics : 12 heures (étape 1), 1 heure (étape 2)
- tj-actions : 3 jours
- chalk : moins de 12 heures
- Nx : 4 heures
- rspack : 1 heure
- num2words : moins de 12 heures
- Kong Ingress Controller : environ 10 jours
- web3.js : 5 heures
- Parmi ces cas, 8 ont eu une durée d’attaque inférieure à une semaine, et la plupart auraient pu être bloqués par un cooldown
Concept et efficacité du cooldown
- Le cooldown consiste à retarder l’utilisation d’une nouvelle dépendance pendant une durée définie après sa publication
- pendant cette période, les fournisseurs de sécurité peuvent détecter si elle est malveillante
- Avantages
- c’est une approche empiriquement efficace, capable de bloquer la majorité des attaques à grande échelle
- la mise en œuvre est très simple et la plupart des outils permettent une configuration gratuite
- exemple avec Dependabot
version: 2 - package-ecosystem: github-actions directory: / schedule: interval: weekly cooldown: default-days: 7 - cela encourage de bons comportements chez les fournisseurs de sécurité : se concentrer sur une détection rapide plutôt que sur des alertes excessives ou la recherche de visibilité
Conclusion et recommandations
- Dans 8 cas sur 10, la durée de l’attaque était d’une semaine ou moins, et un cooldown de 7 jours aurait suffi à bloquer la plupart d’entre eux
- Avec un cooldown de 14 jours, tous les cas sauf xz-utils auraient pu être évités
- Le cooldown n’est pas une solution parfaite, mais c’est un moyen simple de réduire de 80 à 90 % le risque d’exposition
- Au-delà de Dependabot et Renovate, il faut aussi améliorer les choses pour que les gestionnaires de paquets intègrent nativement le cooldown par défaut
- La sécurité de la chaîne d’approvisionnement n’est pas seulement un problème technique, c’est aussi une question de structure de confiance sociale, mais le cooldown reste une mesure d’atténuation réaliste et utile
3 commentaires
En fait, s’il n’y a pas de problème, je pense qu’il vaut mieux ne pas faire la mise à jour.
Faut-il vraiment appliquer à tout prix une nouvelle version qui ne diffère pas beaucoup de la précédente, en acceptant ce risque ?
Avis Hacker News
Les gens craignent d’être exposés à des vulnérabilités graves s’ils ne mettent pas à jour immédiatement, mais en réalité ce n’est généralement pas le cas
Beaucoup de logiciels ne sont pas en déploiement continu : ce sont les clients qui installent eux-mêmes les nouvelles versions, souvent à un rythme de plusieurs semaines ou mois
L’important, c’est la surveillance des dépendances et la vérification des vulnérabilités publiées. Il suffit d’évaluer si le produit est réellement affecté, puis de mettre à jour immédiatement la dépendance concernée uniquement dans ce cas
L’idée s’est répandue qu’il faut forcément mettre à jour le jour même dès qu’une nouvelle version sort
Adopter l’approche « faisons-le maintenant, sinon ce sera plus difficile plus tard » sans examiner les changements réels est inefficace
Rester en permanence à la pointe des numéros de version peut même être contre-productif pour la sécurité
Dans notre entreprise, si le scanner détecte une vulnérabilité critique, il faut mettre à jour sous 7 jours
Si l’échéance est dépassée, cela devient une non-conformité et déclenche une procédure complexe, donc la plupart des gens mettent simplement tout à jour immédiatement
Une application comme un navigateur, qui reçoit beaucoup d’entrées externes, doit être mise à jour souvent, alors qu’une appli météo, dont les entrées sont limitées, est relativement plus sûre
Il est souvent plus efficace de mettre à jour régulièrement tout en appliquant en parallèle des mesures de défense contre les attaques de supply chain
Un modèle de distribution comme Debian stable, où la distribution gère les dépendances communes et effectue une montée de version globale tous les quelques années, paraît de plus en plus raisonnable
Certains écosystèmes évoluent trop vite, ou disposent de systèmes de packaging par distribution insuffisants
Par exemple, il reste difficile d’installer des bibliothèques Node.js via apt pour les utiliser dans un projet
Un écosystème qui bouge vite sans changement fondamental n’est pas sain
JS a connu très peu de progrès réels ces 3 dernières années, mais reconstruire aujourd’hui un projet vieux de 3 ans peut quand même être aussi douloureux qu’une réécriture
Dans des distributions comme Arch, il n’y en a parfois pas du tout
Il y a l’idée qu’instaurer une période de cooldown avant les mises à jour de dépendances, pour empêcher les attaques de supply chain, vaut mieux qu’utiliser immédiatement la toute dernière version pour se protéger des 0-day
La question est de comparer la probabilité qu’une mise à jour introduise une nouvelle vulnérabilité avec celle qu’elle corrige une vulnérabilité existante
Selon SemVer, les versions de correctif sont relativement sûres, donc on peut aussi envisager une courte période de cooldown selon le type de version
Par exemple, si l’on passe de 2.3.4 à 2.4.0, et qu’aucune fonctionnalité urgente n’est nécessaire, mieux vaut attendre la sortie de 2.4.1
La plupart des vulnérabilités ne viennent pas d’attaques intentionnelles, mais de bogues ordinaires
Une politique limitant le nombre et la complexité des dépendances constitue une approche plus forte
Au lieu d’ajouter des bibliothèques « qui font tout », il faudrait n’ajouter que des dépendances petites et à l’objectif clair
Il serait aussi plus simple à gérer si les bibliothèques proposaient des versions LTS ne contenant que des correctifs de sécurité
Réimplémenter soi-même peut être du gaspillage. J’aimerais voir des exemples concrets de bibliothèques problématiques « everything library »
Si l’on fait confiance au même développeur à travers plusieurs bibliothèques, les relations de confiance comptent davantage que le nombre de paquets
La complexité est corrélée aux vulnérabilités, mais n’en est pas la cause directe
Il devient désormais possible de faire des choix tenant compte de la maintenance à long terme plutôt que de la seule vitesse à court terme
Dans le monde du C++, c’est même parfois un argument concurrentiel
La pression pour mettre à jour systématiquement vers la dernière version vient d’une croyance erronée selon laquelle le logiciel s’améliore toujours
En réalité, cela peut simplement remplacer des bogues connus par de nouveaux bogues inconnus
Surveiller les problèmes publics et ne corriger que lorsque c’est nécessaire est une approche raisonnable
C’est donc un cas où une ancienne version était en fait plus sûre
C’est peut-être aussi parce que nous utilisons déjà des logiciels suffisamment stables
Si tout le monde dit « attendons un peu », alors plus personne ne validera rien en premier, un peu comme dans une tragédie des communs
Plus on attend, plus la dette technique s’accumule ; il faut donc des mises à jour progressives et des mesures d’atténuation comme le zero trust et la surveillance
Entre-temps, les scanners de sécurité auront déjà détecté les vulnérabilités
Si un attaquant publie une release non autorisée, on peut parfois le repérer immédiatement
L’idée du cooldown est bonne, mais il existe un risque qu’un attaquant l’exploite pour créer une fausse urgence
En présentant une version comme un « correctif de sécurité urgent », il peut pousser à une installation anticipée alors que cette version est en réalité malveillante
Il faut se préparer à ce type d’attaque par pression psychologique
Une autre raison d’avoir un cooldown est de laisser le temps aux mainteneurs de se rendre compte eux-mêmes d’une compromission
Les attaquants visent les moments où les mainteneurs sont absents — vacances, conférences, jours fériés, etc.
Beaucoup de projets sont gérés par une ou deux personnes, donc ce décalage temporel est crucial
Tout dépend de la nature du projet et de sa surface d’attaque
Il faut en finir avec l’époque où l’on se contentait de suivre des « bonnes pratiques »
La sécurité est comme un sport de contact : il faut réfléchir chaque jour de façon critique aux détails
Il y a aussi une limite du cooldown si l’on considère qu’avec le temps tout devient automatiquement plus sûr
Si personne ne regarde le code, une semaine plus tard le risque reste le même
À la place, une approche de déploiement progressif (gradual rollout) peut être efficace
Chaque consommateur définit un facteur de délai, de sorte que ceux qui tolèrent le plus le risque rencontrent les problèmes en premier,
tandis que les autres restent protégés pendant ce temps
Les mises à jour risquées sont retirées de la file, et les consommateurs retardés ne les voient même jamais
Ces derniers temps, je me demande parfois ce qui est le plus supportable : simplement réinventer la roue, ou gérer un Tetris de dépendances.
Si quelque chose tourne mal dans
for while, il suffit de corriger mon code ; mais dans un Tetris de dépendances, quand une des roues se met soudainement de travers, c’est beaucoup plus difficile à déboguer.