Comment savoir si un script de votre site a une CVE connue ?
npm audit ne voit que ce qui est déclaré dans votre dépôt. Le navigateur exécute aussi les scripts injectés par les tag managers, les plugins CMS et les scripts d’éditeurs tiers. Comment inventorier ce qui tourne réellement en production et le confronter aux bases de CVE, hash par hash.
Un rapport de pentest arrive avec « jQuery 1.12.4 obsolète, CVE-2020-11023 » sur une page où personne ne se souvient d’avoir mis jQuery. Le constat agace. La question qu’il soulève est pire : qu’est-ce qui tourne d’autre sur le site sans que personne ne le sache ?
Pour savoir si un script de votre site a une CVE connue, il vous faut un inventaire de tous les scripts que le navigateur exécute réellement en production, chacun identifié assez précisément pour être confronté à une base de vulnérabilités. L’identifiant précis, c’est le hash cryptographique du fichier. Les scanners de dépendances comme npm audit ou Dependabot ne couvrent que ce qui est déclaré dans votre dépôt. Pour tout le reste, la source fiable est le navigateur lui-même, qui peut remonter le hash SHA-256 de chaque script exécuté, hash qu’on compare ensuite aux builds connus des bibliothèques et à leurs CVE.
Pourquoi npm audit et Dependabot ratent la moitié de vos scripts
Les scanners de dépendances lisent des manifestes. npm audit parcourt votre lockfile, Dependabot surveille votre dépôt, Snyk analyse ce que produit votre build. Tout cela est utile, et rien de tout cela ne répond à la question, parce que l’ensemble des scripts exécutés par un navigateur sur vos pages de production est plus grand que l’ensemble déclaré dans votre dépôt.
La différence vient de tout ce qui s’injecte à l’exécution : les tags posés dans Google Tag Manager par l’équipe marketing, les scripts embarqués dans les plugins CMS et e-commerce, le widget de chat qui charge sa propre copie d’une bibliothèque utilitaire, les scripts d’éditeurs tiers qui en chargent d’autres depuis leurs propres CDN. Rien de tout cela ne passe par package.json. Une partie change sans le moindre déploiement de votre côté.
Nous avons vu une équipe passer un npm audit propre le lundi et échouer un scan PCI le jeudi à cause d’un plugin livré avec un lodash vieux de quatre ans. Les deux outils avaient raison. Ils ne regardaient simplement pas la même chose.
L’écart, dit simplement : ce qui est déclaré contre ce qui s’exécute. Toute détection de vulnérabilités qui ne lit que votre dépôt audite le premier ensemble en espérant qu’il égale le second. D’expérience, ce n’est jamais le cas.
Identifier les scripts par leur hash, pas par leur URL
Une fois admis que le navigateur est la source de vérité, reste le problème de l’identification. Une URL est une preuve faible. /assets/vendor.min.js ne dit rien de son contenu. Un chemin CDN versionné comme .../jquery-3.4.1.min.js vaut mieux, jusqu’au jour où l’éditeur republie d’autres octets au même chemin (ça arrive plus souvent qu’ils ne l’admettent).
Le hash du fichier est l’identité qui ne peut pas mentir. Deux fichiers avec le même SHA-256 sont le même fichier, et chaque build publié de chaque bibliothèque courante a un hash connu et stable. Avec le hash de ce qui s’est exécuté, vous pouvez dire « c’est exactement jQuery 3.4.1 tel que publié » plutôt que « l’URL suggère quelque chose comme jQuery ».
La CSP permet de collecter ces hashes à grande échelle. Le mécanisme report-sha256 (et 384/512) sous les directives script fait inclure aux navigateurs le hash d’intégrité de chaque script chargé dans leurs rapports de violation. Aucun agent dans la page, aucun crawler qui joue au visiteur. De vrais visiteurs, de vraies pages, de vrais hashes, y compris les scripts qui n’apparaissent qu’après connexion ou sur l’étape de paiement, là où un crawler ne va jamais.
Confronter les hashes à une base de CVE
Collecter les hashes, c’est la plomberie. La partie utile, c’est la correspondance. Le Script Inventory de CentralCSP capture chaque script exécuté par le navigateur, avec son origine, son URL, son hash SHA-256/384/512 et le contexte navigateur, et ce sur tous les plans. Sur Scale (349,99 €/mois), la vue Technologies va plus loin : elle prend l’empreinte du contenu de chaque script pour reconnaître la bibliothèque et sa version exacte, confronte cette version aux bases de CVE et lui attribue un statut de cycle de vie (à jour, obsolète, dormante, dépréciée). Vous obtenez une ligne par version de bibliothèque, avec la sévérité, la plage affectée et les fichiers de script concernés, exportable en CSV ou via l’API. Les scripts inline ne sont pas analysés, seuls les fichiers le sont.
La question du jQuery obsolète cesse alors d’être un projet de recherche. L’inventaire liste le fichier, Technologies nomme la version exacte, et CVE-2020-11023 apparaît à côté avec l’advisory à un clic. La mise en place tient dans un en-tête et environ 5 minutes. La mécanique complète est détaillée dans construire un inventaire de scripts sans agent.
Être prévenu quand le script apparaît, pas au prochain pentest
Un inventaire consulté chaque trimestre, c’est un pentest avec des étapes en plus. Les scripts qui font mal sont ceux qui apparaissent entre deux revues : le tag ajouté un mardi, la mise à jour de plugin qui remplace un bundle.
L’alerting de CentralCSP (dès Business, 129,99 €/mois) couvre la première moitié du problème avec une règle « nouvelle origine de script » : la première fois qu’un script se charge depuis une origine jamais remontée par le site, vous recevez un message sur Slack, Microsoft Teams, Google Chat, Telegram ou par email, ou un webhook signé à brancher sur Splunk, Datadog, PagerDuty ou un outil de tickets. Sur Scale, deux règles Technologies couvrent la seconde moitié : « nouvelle vulnérabilité » se déclenche quand une CVE tombe sur une version de bibliothèque que vous exécutez vraiment (avec une sévérité minimale à votre main), et « version obsolète ou dépréciée » quand une mise à jour de plugin remplace un bundle par quelque chose de plus supporté. Les règles s’évaluent à l’ingestion, pas sur un planning, et un délai de repos par règle regroupe les répétitions en un seul message au lieu de les jeter. La fenêtre entre « le script vulnérable est en ligne » et « quelqu’un est au courant » passe de plusieurs mois à quelques minutes.
Ce que cette méthode ne vous dira pas
Deux limites, en toute honnêteté.
L’empreinte de contenu identifie les versions publiées de bibliothèques publiques. Votre code first-party ne correspond à rien, nous ne parierions pas qu’un bundle webpack qui fusionne lodash dans 900 Ko de code applicatif soit reconnu comme du lodash, et les scripts inline ne sont pas analysés du tout. Ces scripts figurent quand même dans l’inventaire avec leur origine et leur hash, donc vous savez qu’ils existent et vous voyez quand ils changent, mais aucune base de CVE n’aura d’avis sur eux. Non reconnu ne veut pas dire sûr. Cela veut dire que c’est à vous de le relire.
Et une correspondance CVE est un indice, pas un verdict. CVE-2020-11023 s’exploite via $.htmlPrefilter avec du HTML non maîtrisé. Si aucun chemin de code de votre page n’y injecte de saisie utilisateur, le risque pratique peut être faible. La correspondance vous dit exactement quel advisory lire et quelle mise à jour planifier. Le jugement reste le vôtre.
Aucune de ces deux limites ne concerne le cas qui ouvre cet article : une bibliothèque publique connue, en production, avec une CVE publiée. C’est exactement ce que l’empreinte de contenu attrape, et elle l’attrape sur chaque page atteinte par de vrais visiteurs, injections de tag manager et bundles de plugins compris.
Ce que l’inventaire change, c’est la forme du problème. Avant, la question du script vulnérable n’avait pas de réponse parce que la liste des scripts était inconnue. Après, c’est une liste finie, avec des hashes, des correspondances, des sévérités et un flux qui prévient quand la liste s’allonge. Ça, une équipe peut le traiter, et le Script Inventory de CentralCSP vous y amène avec un seul en-tête. La liste elle-même est incluse dans tous les plans, et l’essai de 14 jours du plan Start (trois sites, 250 000 rapports par mois) suffit à la construire pour la plupart des sites. La correspondance CVE par-dessus est une fonction du plan Scale : si c’est le rapport de pentest qui vous amène ici, c’est ce palier qu’il faut budgéter.
Questions fréquentes
npm audit peut-il détecter les scripts vulnérables de mon site ?
En partie seulement. npm audit lit votre lockfile, donc il couvre les dépendances que votre build embarque. Un script ajouté via un tag manager, un plugin CMS ou un snippet d'éditeur ne figure jamais dans package.json, et npm audit ne le verra donc jamais. Ce script s'exécute pourtant dans le navigateur de vos visiteurs, et c'est précisément là que se cachent les bibliothèques obsolètes.
Comment connaître la version de jQuery qui tourne réellement sur mon site ?
Identifiez le fichier par son hash, pas par son URL. Une URL comme /js/jquery.min.js ne dit rien de la version, et même un chemin CDN versionné peut être republié. Le hash SHA-256 du fichier identifie le build exact. Les navigateurs peuvent remonter ce hash pour chaque script exécuté via le mécanisme report-sha256 de la CSP, et il suffit ensuite de le confronter aux versions publiées de jQuery et à leurs CVE.
Une CVE dans un script tiers signifie-t-elle que mon site est exploitable ?
Non. Une CVE signifie que la bibliothèque contient une faille connue, pas que vos pages exécutent le code vulnérable avec des données contrôlées par un attaquant. Traitez la correspondance comme une piste priorisée : lisez l'advisory, vérifiez si la fonction touchée est atteignable sur vos pages, puis mettez à jour ou supprimez. Auditeurs et attaquants trouveront la même correspondance que vous, l'ignorer est donc rarement une option.
Faut-il installer un agent ou lancer un crawler pour scanner mes scripts ?
Non. Le reporting de hash de la CSP transforme le navigateur de chaque visiteur en capteur. Vous ajoutez un en-tête de réponse, et les navigateurs remontent l'origine, l'URL et le hash d'intégrité de chaque script exécuté, y compris ceux qu'un crawler ne déclencherait jamais parce qu'ils n'apparaissent qu'après consentement, connexion ou paiement. La mise en place prend environ 5 minutes.