Tous les articles
Incident6 min de lecture

Un seul script tiers a coûté 2,5 M$ aux utilisateurs de Polymarket. Auditez les vôtres dès aujourd’hui

Browser window with a row of puzzle-piece plugins, one glowing orange piece pulling data streams out of a safe

Jeudi dernier, le 29 octobre, Polymarket a basculé sa plateforme d’échange en mode annulation uniquement. Des utilisateurs signalaient des comptes vidés, et en une heure environ l’entreprise a confirmé la cause, selon ses propres mots : « L’un de nos prestataires tiers a été compromis », comme le rapporte The Block.

Ce prestataire, selon le Wall Street Journal cité par Finance Magnates, était DebugBear, un service de monitoring de performance dont le code tournait sur le site de Polymarket. Une analyse blockchain citée dans cet article a retracé plus de 2,5 millions de dollars transférés en plus de 900 transactions vers un portefeuille lié à l’attaquant. Polymarket affirme qu’il indemnisera intégralement les utilisateurs touchés.

Aucun mot de passe n’a fuité. Aucune seed phrase n’a été hameçonnée. Les utilisateurs étaient simplement connectés à un site qui chargeait un fichier JavaScript depuis le serveur de quelqu’un d’autre, et ce fichier a changé.

Si votre app charge de l’analytics, des widgets de chat, de l’A/B testing, du session replay ou du monitoring de performance depuis un CDN tiers, vous confiez à ces prestataires tout ce que votre propre code peut faire. Voici ce qui s’est passé, pourquoi cela vous concerne même si vous ne gérez pas une plateforme d’échange, et comment vérifier votre app dès cette semaine.

Ce qui s’est passé

D’après le compte rendu de SafeState sur l’analyse des chercheurs, l’attaque s’est déroulée à peu près ainsi :

  • Le site de Polymarket chargeait un script de real-user monitoring depuis le CDN de DebugBear. Une configuration tout à fait banale : des milliers de sites font de même avec des dizaines de prestataires.
  • L’attaquant a remplacé ce fichier par du JavaScript malveillant. Comme le navigateur le récupérait à chaque fois directement chez le prestataire, chaque visiteur a reçu la nouvelle version immédiatement.
  • Le script vérifiait la présence d’une session Polymarket active, lisait les données du compte, les informations du portefeuille et les identifiants de l’API de trading dans le stockage du navigateur, puis utilisait le propre processus de signature de Polymarket pour déplacer des positions ou vendre des avoirs vers un portefeuille de l’attaquant.

Le détail qui mérite réflexion : selon les chercheurs, ni les portefeuilles matériels ni les mots de passe robustes n’ont servi à quoi que ce soit, car le script n’a jamais eu besoin d’une clé privée. Il agissait en tant qu’utilisateur connecté, depuis la session de navigateur de l’utilisateur lui-même.

Le correctif de Polymarket, selon le même rapport, a été un commit en production qui a supprimé le script du prestataire. Le trading a repris l’après-midi même. SafeState note aussi que ce n’était pas le premier incident lié à un script tiers à toucher la plateforme cette année.

Pourquoi c’est important pour votre app

Une balise script qui pointe vers cdn.some-vendor.com s’exécute avec les mêmes pouvoirs que votre propre code. Elle peut lire le DOM, lire localStorage et sessionStorage, appeler votre API avec les identifiants de l’utilisateur et modifier ce qu’affiche la page. Il n’y a aucun bac à sable entre « votre code » et « le snippet d’analytics que vous avez collé pendant la semaine du lancement ».

C’est particulièrement vrai pour les apps livrées rapidement et celles construites avec l’IA, car :

  • Les scripts s’accumulent. Chaque prompt du genre « ajoute de l’analytics », « ajoute une bulle de chat », « ajoute du suivi d’erreurs » tend à ajouter une balise de plus. Rares sont ceux qui les retirent ensuite.
  • Les jetons vivent dans le stockage du navigateur. Beaucoup de templates de départ gardent le jeton de session ou d’API dans localStorage, où n’importe quel script de la page peut le lire.
  • Il n’y a généralement pas de Content-Security-Policy. Sans elle, n’importe quel script injecté ou remplacé peut charger du code depuis n’importe où et envoyer des données n’importe où.

Vous ne pouvez pas auditer les pipelines de build de vos prestataires. Mais vous pouvez limiter les dégâts qu’un script malveillant peut causer.

Comment vérifier votre app

1. Listez tous les scripts chargés par vos pages. Ouvrez votre app, puis les DevTools, l’onglet Network, filtrez sur JS et rechargez. Ou exécutez ceci dans la console sur chaque page importante :

[...document.scripts].map(s => s.src || '(inline)').filter(Boolean)

Notez chaque domaine qui ne vous appartient pas. Faites-le aussi sur vos pages accessibles après connexion, pas seulement sur le site vitrine. Les gestionnaires de balises peuvent charger d’autres scripts qui n’apparaissent pas dans votre code source HTML.

2. Vérifiez lesquelles de ces balises ont un attribut integrity. Dans la console :

[...document.querySelectorAll('script[src]')]
  .filter(s => !s.src.startsWith(location.origin))
  .map(s => ({ src: s.src, sri: !!s.integrity }))

3. Regardez vos en-têtes de réponse. Lancez curl -sI https://yourapp.com et cherchez Content-Security-Policy. Si elle est absente, ou si elle contient script-src * ou 'unsafe-inline' sans nonces, elle ne limite pas grand-chose.

4. Vérifiez où vit votre session. Dans les DevTools, regardez sous Application, dans Local Storage et Session Storage. Si vous y voyez un jeton d’accès, un jeton de rafraîchissement ou une clé API, n’importe quel script de la page peut le lire, et c’est exactement ce qu’a fait le payload visant Polymarket.

Comment corriger

Supprimez ce dont vous n’avez pas besoin. C’est l’étape la plus rentable. Les recommandations de SafeState aux opérateurs commencent par tenir un inventaire des scripts tiers et retirer les balises non essentielles des pages de compte, de paiement et de transaction. Vos pages de connexion, de facturation et de paramètres ont rarement besoin d’un outil de heatmap.

Épinglez et vérifiez ce que vous gardez avec Subresource Integrity (SRI). Si un prestataire propose des fichiers versionnés, référencez une version précise et ajoutez un hash. Si le fichier change, le navigateur refuse de l’exécuter :

<script
  src="https://cdn.example-vendor.com/sdk/4.2.1/sdk.min.js"
  integrity="sha384-REPLACE_WITH_REAL_HASH"
  crossorigin="anonymous"></script>

Générez le hash vous-même à partir du fichier exact que vous avez examiné :

curl -s https://cdn.example-vendor.com/sdk/4.2.1/sdk.min.js \
  | openssl dgst -sha384 -binary | openssl base64 -A

Attention au compromis : SRI casse les scripts que les prestataires mettent à jour sur place à une URL non versionnée, ce qui est courant pour les snippets de monitoring et d’analytics. Pour ceux-là, soit vous hébergez vous-même une copie épinglée que vous mettez à jour délibérément, soit vous acceptez de faire confiance au fichier en direct du prestataire et le tenez à l’écart des pages sensibles.

Ajoutez une Content-Security-Policy. CSP indique au navigateur quelles origines peuvent servir des scripts et vers quelles origines la page peut envoyer des données. Un point de départ pour une app Next.js ou Express pourrait ressembler à ceci :

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.example-vendor.com;
  connect-src 'self' https://api.yourapp.com;
  frame-ancestors 'none';
  base-uri 'self';
  object-src 'none'

Soyez réaliste sur ce que CSP vous apporte ici : si le domaine du prestataire lui-même est autorisé, CSP n’arrêtera pas un fichier malveillant servi depuis ce domaine. En revanche, CSP empêche ce script de charger du code depuis de nouveaux domaines ou d’envoyer des données volées vers des endpoints arbitraires absents de connect-src. Déployez-la d’abord avec Content-Security-Policy-Report-Only pour voir ce qui casse. Notre guide pour ajouter une Content-Security-Policy détaille la démarche pas à pas.

Content-Security-Policy : le guide de configuration complet

Mettez les jetons de session hors de portée de JavaScript. Placez les jetons de session dans des cookies marqués HttpOnly, Secure et SameSite, pour que les scripts de la page ne puissent pas les lire. Cela n’empêchera pas un script malveillant d’envoyer des requêtes au nom de l’utilisateur tant que la page est ouverte, mais cela empêche que les jetons soient dérobés et réutilisés ailleurs. Voir les attributs de cookie Secure, HttpOnly et SameSite.

Gardez vos propres clés frontend propres. Si un script peut lire votre bundle, il peut lire tout secret que vous y avez livré, et des bots traquent déjà les clés et fichiers .env qui fuitent. Consultez notre guide sur les clés API exposées dans le JavaScript côté client, assurez-vous de ne pas livrer de source maps en production, et si vous avez déjà eu une frayeur, notre article sur la rotation des secrets de votre app sans interruption de service explique comment faire le ménage.

Secure, HttpOnly et SameSite : verrouillez vos cookies de session

Checklist à retenir

Avant de fermer les DevTools, assurez-vous de :

  • Lister chaque script tiers sur vos pages publiques et vos pages connectées.
  • Supprimer les scripts que vous n’utilisez plus, surtout sur les pages de connexion, de facturation et de compte.
  • Épingler les versions et ajouter des hashes d’intégrité là où les prestataires le permettent.
  • Héberger vous-même, ou tenir à l’écart des pages sensibles, les scripts impossibles à épingler.
  • Livrer une Content-Security-Policy avec des script-src et connect-src stricts.
  • Garder les jetons de session dans des cookies HttpOnly, pas dans localStorage.
  • Refaire cet audit chaque fois que quelqu’un (ou un agent IA) ajoute une nouvelle balise.

Découvrez où vous en êtes

Si vous voulez un point de départ rapide, AppSafe vérifie vos en-têtes de sécurité, y compris la présence d’une Content-Security-Policy, et son scan complet vérifié examine les problèmes frontend comme Subresource Integrity et les bibliothèques JavaScript vulnérables. Vous pouvez voir à quoi ressemble un rapport dans le rapport d’exemple, ou combiner cela avec notre checklist avant lancement pour les apps construites avec l’IA.

Polymarket a identifié la cause environ une heure après avoir suspendu le trading. Beaucoup de petites équipes mettraient bien plus longtemps à remarquer qu’un script a changé. Lancez un scan AppSafe gratuit de votre app dès aujourd’hui et découvrez où vous en êtes.

Lancez cette checklist automatiquement

Un seul scan gratuit vérifie votre app pour tous ces problèmes et vous donne un prompt de correction IA pour chaque résultat.

Scanner mon app gratuitement