En bref
Aller directement à la version longue ↓
Qu’est-ce qui s’est passé ? Le 20 août 2026, un pirate a utilisé le compte crates.io d’un mainteneur légitime pour publier des versions malveillantes d’arrayref 0.3.10, d’append-only-vec 0.1.9 et d’internment 0.8.7. Le code des bibliothèques dans ces trois cas était sain. Chacune d’entre elles comportait une dépendance à « proc-macro1 », un typosquat de « proc-macro2 » de David Tolnay, dont le fichier build.rs reconstruisait les adresses de l’attaquant à partir d’un encodage base64, téléchargeait un binaire de deuxième étape via TLS sans vérification de certificat, puis l’exécutait lors de la compilation.
Impact : « arrayref » totalise 245 millions de téléchargements depuis sa création sur crates.io, dont environ 53,7 millions au cours des 90 derniers jours, et 403 crates le mentionnent comme dépendance directe. La plupart des équipes ne l’ont jamais sélectionné, car il est intégré aux bibliothèques de cryptographie, de graphisme et de blockchain. L’analyse de StepSecurity estime que la fenêtre d’exposition d’arrayref a duré 86 minutes, depuis sa publication à 07 h 15 UTC jusqu’à son retrait de l’index à 08 h 41 UTC. append-only-vec et internment sont moins importants, avec respectivement environ 4,5 millions et 14,4 millions de téléchargements cumulés, et ont été retirés au cours de la même vague.
Situation : crates.io a supprimé six crates créées par des pirates (proc-macro1, proc-macro-en, aovine, arone, aronenao, tinymember), a retiré les trois versions malveillantes et a verrouillé le compte du responsable de maintenance. L'équipe de réponse aux incidents de sécurité de Rust indique que « l'ordinateur ou les identifiants de l'auteur ont probablement été compromis ». Le nettoyage du registre ne suffit pas à nettoyer vos machines. Les caches Cargo, les arborescences de dépendances, les couches de conteneurs, les images CI et les miroirs peuvent encore les contenir. Si un hôte a résolu ce matin-là un nouveau graphe de dépendances qui aurait pu atteindre l'une de ces crates, considérez-le comme suspect jusqu'à ce que vous l'ayez vérifié.
La version longue
Suis-je concerné ?
Deux questions se posent ici, et leurs réponses sont différentes. La fenêtre d’acquisition des versions malveillantes s’est refermée lorsque crates.io les a supprimées ce matin-là. Ce n’est pas le cas de la fenêtre d’exécution. Le retrait et la suppression ne purgent rien de ce qui se trouve déjà sur le disque ; ainsi, un cache Cargo, une arborescence de dépendances fournies par un fournisseur, un miroir de registre ou une couche de conteneur ayant conservé l’une de ces versions peut toujours l’intégrer dans une compilation ultérieure. L’acquisition était limitée dans le temps. L’exécution, quant à elle, relève de vos machines.
À l'acquisition, le groupe concerné est plus restreint que ne le laissent supposer les chiffres de téléchargement. Un fichier Cargo.lock validé fixe des versions précises, et Cargo maintient les dépendances verrouillées sur les versions indiquées dans ce fichier lorsque cela est possible; ainsi, un projet compilé à partir d’un fichier lock inchangé est resté sur la version 0.3.9 d’arrayref et n’a jamais vu la version 0.3.10. Cela vaut également pour un conteneur « clean ». Un système de fichiers vierge ne change rien si la compilation copie un fichier de verrouillage valide, ce qui est le cas habituellement.
Ce qui a réellement mis les équipes en danger, c’est la ré-évaluation des dépendances alors que les versions malveillantes étaient encore actives, sur un graphe de dépendances pouvant atteindre l’un des crates affectés. Toute personne ayant exécuté `cargo update` dans cette situation était exposée, tout comme celles dont le bot de gestion des dépendances avait ouvert et fusionné une pull request de mise à jour ce matin-là. Il en allait de même pour toute personne dont la compilation s’était exécutée sans le fichier de verrouillage, ce qui constitue le véritable risque pour les conteneurs, puisqu’un Dockerfile qui ne copie que le manifeste, ou un fichier `.dockerignore` qui exclut `Cargo.lock`, effectue une résolution à partir de zéro à chaque fois. Il en va de même pour toute personne dont le manifeste a changé ce matin-là et a introduit un nouveau chemin d’accès à `arrayref`, car Cargo résout à nouveau la partie affectée du graphe et écrit un nouveau fichier de verrouillage. Et pour toute personne ayant exécuté `cargo install`, qui ignore un fichier de verrouillage publié à moins que vous ne passiez l’option `–locked`.
Les auteurs de bibliothèques étaient davantage exposés que ceux d’applications, bien qu’aucune règle ne l’imposât. L’équipe Cargo a abandonné en août 2023 l’ancienne recommandation selon laquelle « les binaires incluent les fichiers de verrouillage lors du commit, contrairement aux bibliothèques », et depuis lors, la commande `cargo new` prend en compte le fichier `Cargo.lock` pour les bibliothèques. Cependant, de nombreux dépôts de bibliothèques sont antérieurs à ce changement et continuent d’être résolus à la volée lors de l’intégration continue (CI). De plus, un fichier de verrouillage n’est pas transmis en aval. Votre fichier de verrouillage n’affecte pas les utilisateurs de votre crate ; seul votre fichier `Cargo.toml` a un impact sur eux.
Commencez par le cache Cargo local. L'équipe d'intervention en matière de sécurité de Rust a publié cette vérification:
find ~/.cargo/registry/cache -type f -name'append-only-vec-0.1.9.crate'-o-name'arrayref-0.3.10.crate'-o-name'internment-0.8.7.crate'-o-name'proc-macro1-*.crate'-o-name'proc-macro-en-*.crate'-o-name'aovine-*.crate'-o-name'arone-*.crate'-o-name'aronenao-*.crate'-o-name'tinymember-*.crate' -print
Vérifiez ensuite si vos fichiers de verrouillage contiennent ces mêmes versions et s’ils comportent une entrée « proc-macro1 ». La présence d’une ligne « proc-macro1 » dans un fichier Cargo.lock constitue en soi un indice, car « proc-macro1 » a été créé par le pirate pour cette campagne et a été supprimé du registre.
En quoi consistent les attaques « arrayref » et « proc-macro1 » ?
La mise en place a commencé plusieurs heures avant le déploiement de la charge utile. D’après la chronologie établie par StepSecurity, un compte GitHub nommé « dtolney » a été créé à 01 h 17 UTC, usurpant l’identité de David Tolnay, qui gère le véritable proc-macro2. Un compte crates.io correspondant a suivi à 01 h 25 UTC. À 01 h 55 UTC, ce compte a publié proc-macro1 1.0.106, une copie « propre » de proc-macro2 comportant des métadonnées d’auteur falsifiées. Cette version n’avait aucun effet malveillant. Elle est restée en ligne pendant plus de cinq heures, accumulant un peu d’historique dans le registre et passant pour un crate tout à fait banal.
À 07 h 11 UTC, ce même compte a publié « proc-macro1 1.0.107 ». Il s’agissait de la version exploitée. Quatre minutes plus tard, à 07 h 15 UTC, « arrayref 0.3.10 » est apparu sur le compte du propriétaire légitime, comportant une dépendance vis-à-vis du typosquat. « internment 0.8.7 » a suivi à 07 h 34 UTC, puis « append-only-vec 0.1.9 » à 07 h 37 UTC.
Le rapport de l’équipe d’intervention en matière de sécurité de Rust, publié le jour même, indique avoir reçu à 07 h 15 UTC un signalement signalant que « proc-macro1 » était malveillant, et précise que la crate populaire « arrayref » avait récemment été republiée et renvoyait vers celle-ci. La découverte initiale revient à l’équipe de recherche de Nextron Systems GmbH. Par ailleurs, un contributeur utilisant le pseudonyme jhobern a signalé le problème dans la base de données d’avis de RustSec et indique avoir également envoyé un e-mail à l’équipe de sécurité de Rust. D’après la reconstitution effectuée par StepSecurity, ce rapport RustSec date de 07 h 54 UTC, soit 39 minutes après la publication malveillante, ce qui suggère que les deux rapports décrivent des voies de notification différentes plutôt que la même.
Le nettoyage a débuté à 08 h 03 UTC avec la suppression de proc-macro1. L'équipe d'intervention de sécurité de Rust a depuis publié les heures exactes de suppression des trois crates légitimes. arrayref 0.3.10 a été retiré à 08 h 41 UTC après 86 minutes en ligne, internment 0.8.7 à 09 h 04 UTC après 90 minutes, et append-only-vec 0.1.9 à 09 h 25 UTC après 107 minutes. Le journal de crates.io indique que les versions valides que l’attaquant avait supprimées ont été restaurées entre 09 h 15 et 09 h 26 UTC pour les trois crates.
Comment le « Yank » est devenu le « Lure »
Les incidents npm précédents n’avaient rien de comparable à ce qui s’est passé ensuite. Dans la minute qui a suivi la publication de la version 0.3.10, le pirate a utilisé le compte du propriétaire pour « yanker » toutes les versions récentes d’arrayref à l’aide d’un script, à raison d’une opération toutes les quatre secondes environ. Le « yanking » dans Cargo ne supprime pas une version. Les fichiers de verrouillage existants qui font déjà référence à une version « yankée » continuent à être compilés, et le résolveur ignore les versions « yankées » uniquement lorsqu’il sélectionne de nouvelles versions. L'historique des versions de crates.io indique que les versions 0.3.5 à 0.3.9 ont été supprimées entre 07 h 15 min 24 s et 07 h 15 min 40 s UTC depuis le compte du propriétaire, puis rétablies entre 09 h 15 et 09 h 17 UTC par un membre de l'équipe de crates.io. La même chose s’est produite pour les versions 0.1.7 et 0.1.8 d’append-only-vec à 07 h 38 UTC, ainsi que pour les versions 0.8.3 à 0.8.6 d’internment à 07 h 37 UTC.
Cela transforme une opération de maintenance de registre courante en un levier d’ingénierie sociale, et sa portée était suffisamment restreinte pour que cela fonctionne. Le retrait ne notifie personne, et un projet déjà verrouillé sur une version retirée continue à être compilé avec celle-ci. L’avertissement concernant la version retirée s’affiche dès que Cargo consulte l’index du registre, lors de commandes telles que `cargo update`, `cargo add` ou `cargo install --locked` ; il s’adresse donc en premier lieu aux personnes les plus susceptibles de procéder à une nouvelle résolution. Pour elles, la version malveillante devenait la version la plus récente acceptée par Cargo, et pour les exigences de compatibilité courantes telles que `^0.3.5`, c’était la seule candidate restante qui n’avait pas été retirée. Un développeur qui a vu l’avertissement et a exécuté `cargo update` pour le faire disparaître pouvait raisonnablement penser qu’il agissait de manière responsable.
À l'intérieur du script de compilation
Le code malveillant se trouvait dans le fichier `build.rs` de proc-macro1, et non dans arrayref. Quiconque aurait consulté le code source d’arrayref 0.3.10 pour vérifier aurait trouvé les mêmes macros de référence de tableau que la crate a toujours contenues, ainsi qu’une ligne de dépendance dans le fichier `Cargo.toml`.
Le script de compilation a assemblé ses informations de commande et de contrôle à partir de fragments Base64 plutôt que d’utiliser une URL lisible codée en dur, puis a récupéré un binaire de deuxième étape depuis https://23.254.165.112:9089/ via TLS, la validation du certificat étant explicitement désactivée. Le téléchargeur utilisait rustls avec un vérificateur « accept-all » dont les méthodes de vérification renvoyaient un résultat positif sans condition ; la connexion était donc chiffrée, mais n’offrait aucune authentification effective du serveur. Une deuxième adresse sur le même hôte, 23.254.165.112:443, a été transmise à la charge utile en tant que point de terminaison de commande et de contrôle. Le nom d’hôte associé signalé par StepSecurity est hwsrv-798836.hostwindsdns.com.
L'exécution variait selon la plateforme. Sur les systèmes Unix, la charge utile était écrite dans /tmp/rust-setup, rendue exécutable, puis lancée en tant que processus détaché. Sous Windows, elle plaçait le fichier %TEMP%\rust-setup.ps1 dans le répertoire %TEMP% et le lançait via %TEMP%\rust-setup-launch.vbs à l'aide de wscript.exe avec l'indicateur CREATE_NO_WINDOW, de sorte qu'aucun élément visible n'apparaissait à l'écran.
Deux éléments ont permis de passer cela inaperçu. Premièrement, comme le souligne StepSecurity, le code de la bibliothèque contenu dans « proc-macro1 » correspond en réalité à « proc-macro2 » ; par conséquent, les compilations aboutissent et rien d’anormal n’apparaît dans la sortie standard. Deuxièmement, aucune partie de votre programme n’avait besoin d’appeler une fonction « arrayref », et vous n’aviez pas besoin d’exécuter spécifiquement la commande `cargo build`. Cargo ne dispose d’aucun hook au moment de l’installation ; par conséquent, rien ne s’exécutait au moment du téléchargement, mais un script de compilation s’exécute juste avant la compilation du paquet pour toute commande qui compile le graphe. Les commandes `cargo check`, `cargo test`, `cargo clippy` et `cargo doc` suffisaient chacune à elles seules, au niveau de privilèges de la personne ou du processus qui les lançait. Sur un ordinateur portable de développeur, il s’agit d’une session utilisateur pouvant accéder à des clés SSH, des identifiants de cloud et des données de navigateur. En CI, il s’agit d’un runner détenant des jetons de registre, des éléments de signature et des rôles cloud.
Pendant les premières heures, personne ne pouvait dire ce que faisait réellement le binaire de phase 2. Les rapports de RustSec, StepSecurity et SafeDep décrivaient le téléchargeur et les adresses de commande et de contrôle, mais s’arrêtaient là. Cette lacune a désormais été comblée. Wiz Research a analysé les charges utiles récupérées et décrit une porte dérobée performante. Elle envoie des balises via HTTPS, renvoie les informations sur l’hôte et les identifiants volés sous forme de JSON encodé en base64, lit les identifiants enregistrés dans les bases de données SQLite de Chrome, Brave et Edge, et s’installe de manière persistante en tant que service utilisateur systemd sous Linux, LaunchAgent sous macOS ou clé Run du registre sous Windows. Elle prend en charge quatre commandes, dont l’une permet de récupérer et d’exécuter un shell ou PowerShell arbitraire, et elle se rabat sur un algorithme de génération de domaines si le C2 principal cesse de répondre. Il convient donc de considérer toute exécution confirmée comme une compromission de l’hôte et de partir du principe que toutes les informations d’identification auxquelles cet hôte ou ce programme d’exécution a pu accéder ont potentiellement été compromises, y compris les mots de passe enregistrés dans un navigateur.
Le volet « infrastructure » s’est également révélé plus intéressant. La campagne Mastra comparable, menée en juin, avait diffusé un voleur d’informations multiplateforme qui ciblait les identifiants, les données de navigateur et les portefeuilles de cryptomonnaies. Wiz signale que les charges utiles arrayref envoient des signaux vers le même chemin de requête C2 que celui utilisé par Mastra, que l’adresse de balise partage un émetteur de certificat TLS avec un hôte Mastra, et qu’un C2 signalé par une victime dans le cadre de cet incident apparaît dans les informations de Google sur les menaces concernant la campagne « axios » menée par un acteur nord-coréen. Microsoft a attribué l’activité de Mastra à un groupe nord-coréen. Il s’agit là d’un chevauchement documenté des infrastructures plutôt que d’une attribution formelle pour cet incident, et cela va bien au-delà du simple fait que deux campagnes partagent un même hébergeur.
Pourquoi le modèle de compilation de Rust aggrave encore la situation
Rust jouit d'une solide réputation en matière de sécurité, et celle-ci est justifiée dans les domaines où le langage intervient réellement. La sécurité de la mémoire et un système de types strict n'empêchent pas une dépendance d'exécuter délibérément du code arbitraire avant que votre programme ne se termine.
Cargo propose à une dépendance deux façons d'exécuter du code sur votre machine au moment de la compilation. La première est le fichier `build.rs`. En plaçant ce fichier à la racine d'un paquet, Cargo compile le script et l'exécute « juste avant de compiler le paquet ». Ses utilisations légitimes sont réelles et courantes : elles couvrent la compilation de bibliothèques C, la génération de code et la détection de fonctionnalités, ce qui explique précisément pourquoi elles passent inaperçues dans un diff. La deuxième méthode consiste à utiliser des macros procédurales, qui sont compilées et exécutées au sein du compilateur pendant la compilation de votre code. La documentation de référence de Rust est claire quant aux conséquences : les macros proc « présentent les mêmes risques de sécurité que les scripts de compilation de Cargo ». Aucune de ces deux interfaces n’est isolée dans un bac à sable.
L'écosystème npm propose au moins la commande « npm install –ignore-scripts », qui constitue la mesure de protection mentionnée ici à la suite de l'attaque Mastra. Cargo ne dispose d'aucun équivalent général, ni dans la version stable ni dans la version nightly. Il n’existe aucun indicateur de compilation, aucune clé de configuration ni aucun indicateur -Z permettant simplement de désactiver les scripts de compilation de dépendances arbitraires tout en laissant le reste de la compilation se poursuivre, et leur mise en sandbox fait l’objet d’un ticket ouvert sur Cargo depuis 2018. La seule option de désactivation documentée est très limitée. Un paquet déclarant une clé « links » peut voir son script de compilation remplacé dans la configuration; dans ce cas, le script n’est ni compilé ni exécuté. Cela concerne les crates -sys fournissant des métadonnées de bibliothèques C, et non un crate tel que proc-macro1. La raison profonde pour laquelle aucun indicateur général n’existe est qu’un script de compilation n’est pas une fonctionnalité facultative au moment de l’installation, contrairement à un hook « postinstall » de npm. Il fait partie intégrante de la compilation, et le désactiver signifie généralement que le paquet ne se compile pas.
La distance transitive a également son importance. « arrayref » est normalement un crate utilitaire « feuille » qui fournit des macros permettant d’obtenir des références de tableaux, et ses versions « clean » ne déclarent aucune dépendance d’exécution ou de compilation, seulement une dépendance de développement vis-à-vis de « quickcheck ». C’est ce qui rendait la version 0.3.10 atypique, puisque « proc-macro1 » était la première dépendance ordinaire que ce crate ait jamais déclarée. Parmi ses dépendances directes figurent tiny-skia-path, tiny-skia, libsecp256k1, multiaddr et schnorrkel ; l’analyse des dépendances inverses effectuée par StepSecurity ajoute revm-precompile du côté d’Ethereum, ainsi que solana-runtime et spl-token du côté de Solana. L’une des dépendances les plus exposées était blake3, avec environ 166 millions de téléchargements cumulés, qui nécessitait arrayref ^0.3.5 jusqu’à la version 1.8.6, puis a été livrée en version 1.8.7 sans cette dépendance à 09 h 09 UTC le même matin. Au-dessus de ces crates se trouvent bien d’autres projets qui les utilisent. Presque aucune des cibles de cette attaque ne disposait d’arrayref dans son propre fichier Cargo.toml. Elles utilisaient une bibliothèque graphique ou cryptographique, et cette dépendance s’y trouvait en aval, deux ou trois niveaux plus bas, dans une crate sur laquelle elles n’avaient aucune raison de se prononcer.
Ce qui devrait changer votre façon d'envisager la prise en charge initiale
Tous les contrôles qui auraient pu détecter cette erreur portent sur autre chose que l'artefact final.
Le « version pinning » a été utile, et il s’agit d’une véritable protection, mais c’est une protection qui repose sur l’inertie. Elle fonctionne jusqu’au moment où vous avez besoin d’une mise à jour de sécurité ; à ce moment-là, vous résolvez les nouvelles dépendances et héritez de ce que le registre fournit à cet instant précis. La signature et l’authentification à deux facteurs constituent des contrôles de publication précieux, mais aucun des deux ne garantit que le code publié est inoffensif. L’équipe de réponse aux incidents de sécurité de Rust a indiqué que l’ordinateur ou les identifiants du responsable de maintenance avaient probablement été compromis, sans préciser publiquement lesquels ; il n’est donc pas encore possible de déterminer quel contrôle de publication aurait pu empêcher cet incident. Une analyse de composition basée sur les identifiants CVE n’a révélé aucune correspondance, car aucun CVE n’existait pendant la période d’exposition. Une SBOM aurait pu être utile, mais uniquement à des fins d’inventaire. « proc-macro1 » a été ajouté à « arrayref » en tant que dépendance ordinaire plutôt que comme dépendance réservée à la compilation ; ainsi, une SBOM générée avant la compilation, qu’elle soit basée sur Cargo ou dérivée d’un fichier de verrouillage, aurait répertorié « proc-macro1 1.0.107 » comme un composant nouvellement résolu. En revanche, elle n’aurait pas permis de déterminer que ce composant était malveillant. À un niveau inférieur, les crates qui effectuaient effectivement le téléchargement, à savoir base64, rustls et ureq, étaient des dépendances de compilation de proc-macro1, et un SBOM dérivé d’un artefact ou axé sur l’exécution aurait pu les omettre complètement.
La révision à la source aurait échoué au niveau de détail auquel la plupart des équipes travaillent réellement, pour la même raison qu’elle a échoué lors de l’attaque Mastra. Un réviseur parcourant le code de la bibliothèque d’arrayref 0.3.10 y trouve des macros propres et familières. Le comportement malveillant se trouve un niveau plus bas, dans un script de compilation appartenant à une dépendance qu’aucun membre de l’équipe n’a choisie, dans une crate dont le code de bibliothèque est une copie d’un élément légitime. Un réviseur consultant l’intégralité du diff de la version, manifeste compris, aurait repéré une nouvelle dépendance dont le nom différait d’un chiffre de « proc-macro2 » et aurait ainsi eu un point de départ. C’est ce niveau de révision qu’il convient d’institutionnaliser, et ce n’est pas celui auquel opèrent la plupart des processus de validation.
Ce qui distingue une position défendable d'une position indéfendable, c'est le moment où la décision relative à la confiance est prise. Si l'on attend que la compilation ait déjà commencé, il sera probablement trop tard, car le paquet se trouve alors sur l'hôte de compilation et son script de compilation a peut-être déjà été exécuté.
Atténuation
Suivez ces étapes dans l'ordre, car les trois premières déterminent si les étapes suivantes relèvent de la mise en ordre ou de la gestion des incidents.
Recherchez les artefacts. Exécutez la vérification du cache Cargo décrite ci-dessus sur les machines des développeurs, les agents de compilation et toutes les images de conteneur créées le 20 août 2026. Recherchez ensuite dans les fichiers de verrouillage de tous les dépôts les références « arrayref 0.3.10 », « append-only-vec 0.1.9 », « internment 0.8.7 », ainsi que toute occurrence de « proc-macro1 », « proc-macro-en », « aovine », « arone », « aronenao » ou « tinymember ».
Vérifiez par rapport aux hachages connus. StepSecurity a publié les valeurs SHA-256 des archives. La valeur pour arrayref 0.3.10 est 25ad700976873c76af785cb99b33c48db7df8b81f21d1e9e06b3676b9a9373ae, celui de proc-macro1 1.0.107 est 61198155da51b838772eecf5bfaac6cbc4dcc388dccc56658fc28a8e831b34d4, et la version « leurre » propre de proc-macro1 1.0.106 est b5c1b5b0763a8809a644a8f92224653f0aca623a98eecc714d27f74b80fbe436. Limitez votre recherche à ces trois éléments. La version 1.0.106 ne contenait aucune charge utile ; sa détection n’indique donc pas une exécution ni un quelconque contact avec l’infrastructure de l’attaquant, mais elle constitue un indice utile montrant qu’un élément de l’environnement a résolu le typosquat pendant sa phase de préparation.
Recherchez l’exécution, pas seulement la présence. Sur les terminaux, recherchez /tmp/rust-setup sur les hôtes Unix, et rust-setup.ps1 ou rust-setup-launch.vbs dans %TEMP% sous Windows. Ce sont les noms locaux que le dropper écrit. Dans les données de télémétrie de proxy, de réseau ou de téléchargement, recherchez séparément les requêtes portant les noms rust-crate_0.1.0 à rust-crate_0.4.0, qui correspondent aux noms de fichiers de la phase 2 spécifiques à la cible que le téléchargeur sélectionne côté distant, plutôt que les fichiers apparaissant sur le disque sous ces noms. Vérifiez si le fichier wscript.exe a été lancé à partir d’un processus de compilation, et s’il existe un processus fils ayant survécu à l’étape de compilation. La présence d’un fichier .crate mis en cache indique que l’archive a été téléchargée. Les artefacts du dropper sur le disque signifient que le script de compilation s’est exécuté suffisamment longtemps pour écrire la charge utile, et ce sont les données de télémétrie du processus ou du réseau qui confirment que la charge utile elle-même a été lancée. En cas de suspicion d’exécution, recherchez également les mécanismes de persistance installés par la porte dérobée, à savoir un service utilisateur systemd non reconnu sous Linux, un LaunchAgent errant sous macOS ou une entrée HKCU Run inattendue sous Windows.
Bloquez et vérifiez les sorties. Ajoutez l’adresse 23.254.165.112 à vos listes de blocage, ainsi que les adresses 23.254.167.107 et 23.254.167.216, toutes deux signalées comme des adresses de commande et de contrôle de niveau 2, et ne limitez pas la portée des règles aux ports 9089 et 443 observés. Ajoutez également hwsrv-798836.hostwindsdns.com, puis examinez rétrospectivement les journaux du proxy et du pare-feu à la recherche de connexions provenant d’hôtes de compilation, y compris les requêtes HTTPS vers le chemin /49890878, qui correspond au point de terminaison de la balise de la phase 2. Un processus de compilation atteignant une adresse IP nue sur un port non standard constitue un événement hautement suspect qui mérite d’être examiné, indépendamment de cette campagne.
Appliquez la rotation à tout ce à quoi le build a pu avoir accès. Si la charge utile s'est exécutée, limitez la rotation à ce que détenait cette identité plutôt qu'à ce qu'a fait le crate. Sur une machine de développeur, cela concerne les clés SSH, les identifiants de connexion au cloud, les jetons de registre, tous les secrets exposés via l'environnement du shell, ainsi que les identifiants de connexion enregistrés dans Chrome, Brave ou Edge, puisque la charge utile les lit directement. En CI, cela concerne les secrets des runners, les relations de confiance OIDC, les jetons de publication de paquets et les éléments de signature.
Définissez correctement la dépendance transitive. arrayref 0.3.9 est « clean », n’a pas été supprimé et est à nouveau disponible. Presque personne n’a arrayref dans son propre manifeste ; par conséquent, arrayref = « =0.3.9 » revient à ajouter une dépendance directe que vous n’utilisez pas autrement, et une définition exacte peut rendre le graphe insatisfaisable par la suite. Pour une dépendance transitive, la solution la plus propre consiste à exécuter `cargo update -p arrayref –precise 0.3.9`, puis à valider et appliquer le fichier de verrouillage résultant.
Rendre le respect du fichier de verrouillage obligatoire. Exécuter la CI avec l’option –locked, qui indique à Cargo d’échouer plutôt que de modifier le fichier de verrouillage, de sorte qu’une résolution inattendue interrompe la compilation au lieu d’aboutir à une mise en production. Exiger une vérification humaine des pull requests générées par le bot de dépendances qui modifient des versions transitives, et considérer toute invitation à mettre à jour — qu’il s’agisse d’un avertissement « yank », d’une pull request générée par un bot ou d’un job de CI en rouge — comme une raison de consulter le diff plutôt que de relancer la résolution.
Détection
Observez le comportement de la compilation. À 07 h 11 UTC, les flux CVE et les bases de données de vulnérabilités ne contenaient aucune information sur « proc-macro1 », et les données de réputation ne disposaient que de cinq heures d’historique du registre. Le comportement était donc le seul indice disponible. Une activité réseau sortante inattendue provenant d’un script de compilation mérite d’être considérée comme un élément pertinent en matière de sécurité, et un processus de compilation qui écrit un exécutable dans un répertoire temporaire puis le détache constitue en soi un indicateur fort.
Veillez à bien respecter la lignée du processus lorsque vous écrivez la règle. Un script de compilation s'exécute sous la forme d'un exécutable compilé distinct lancé par Cargo, généralement nommé « build-script-build » dans le répertoire cible ; le signal est donc émis par un processus fils éphémère de Cargo qui accède au réseau, et non par Cargo lui-même. Une macro procédurale s'exécuterait quant à elle à l'intérieur de rustc. Déclenchez une alerte dans les deux cas, ainsi que lorsque de nouveaux exécutables apparaissent dans /tmp ou %TEMP% pendant une compilation.
Sur les hôtes où l'on soupçonne une exécution, étendez la recherche au-delà de la phase de compilation. La porte dérobée de phase 2 persiste via un service utilisateur systemd, un LaunchAgent ou une clé Run du registre, selon la plateforme, et envoie des signaux via HTTPS ; par conséquent, toute persistance inattendue détectée aux alentours du moment de la compilation mérite à elle seule de déclencher une alerte.
Au niveau de la couche des dépendances, surveillez toute nouvelle dépendance apparaissant dans un diff de fichier de verrouillage, en particulier celles dont le nom ne diffère que d’un caractère ou d’un chiffre par rapport à une crate largement utilisée. La différence entre « proc-macro1 » et « proc-macro2 » est la clé, et elle est clairement visible dans un diff si quelqu’un consulte les diffs de dépendances. Notez que les crates de téléchargement au moment de la compilation utilisées dans cette campagne, à savoir `base64`, `rustls` et `ureq`, ont été déclarées un niveau plus bas à l’intérieur de `proc-macro1` ; par conséquent, une règle qui inspecte uniquement vos propres dépendances directes de compilation ne les aurait pas détectées.
Comment SecureChain pour Rust change la donne
Si cette attaque a fonctionné, c'est parce que l'exécution lors de la compilation constitue une étape considérée comme fiable et qui n'est pas vérifiée dans la quasi-totalité des pipelines Rust. C'est précisément cette faille que SecureChain for Rust a été conçu pour combler.
SecureChain recompile chaque paquet à partir du code source dans des environnements renforcés et isolés, et examine spécifiquement le code source des crates à la recherche de schémas d’attaque au moment de la compilation, notamment les scripts build.rs malveillants et les macros procédurales, bloquant ainsi les crates affectées avant qu’elles n’entrent dans votre chaîne d’outils. Cette distinction est ici plus importante que dans le cadre d’un incident npm classique. En l’absence d’équivalent de l’option –ignore-scripts dans Cargo, une équipe Rust ne peut pas désactiver les scripts de compilation ; l’analyse doit donc avoir lieu en amont plutôt que d’être désactivée au sein du pipeline. Un fichier `build.rs` qui reconstitue une URL à partir de fragments base64, désactive la validation des certificats et récupère un binaire à partir d’une simple adresse IP est le type de schéma d’attaque au moment de la compilation que cette vérification est censée détecter ; l’examiner avant que le crate n’atteigne l’ordinateur portable d’un développeur est bien plus sûr que de le laisser s’exécuter d’abord pour enquêter ensuite.
Découvrez SecureChain pour Rust →
SecureChain fonctionne comme une alternative vérifiée et recompilée à partir du code source au téléchargement direct depuis crates.io, plutôt que comme un scanner ajouté en bout de chaîne. Les paquets sont analysés à la recherche de logiciels malveillants connus, de modèles de code suspects et de risques liés à la chaîne d’approvisionnement ; tout élément ne passant pas l’inspection est bloqué avant d’atteindre votre environnement. Les risques spécifiques à Rust qu’il identifie sont ceux mis en évidence par cet incident, notamment les crates sosies, les éditeurs compromis, les versions altérées, les comportements cachés et la prise de contrôle de crates non maintenus, où la propriété d’un crate abandonné est transférée puis utilisée pour diffuser une mise à jour malveillante sous un nom auparavant considéré comme fiable. Une crate dont le nom diffère d’un seul chiffre par rapport à une dépendance fondamentale correspond exactement au modèle de « sosie » qu’un modèle de filtrage est conçu pour traiter comme suspect, et ce risque existait avant même qu’une compilation ne l’utilise.
Tout cela ne change en rien la façon dont les développeurs travaillent. SecureChain est un remplacement direct de référentiel compatible avec JFrog Artifactory, Sonatype Nexus et GitHub Packages, et les équipes conservent les mêmes paquets, les mêmes API et les mêmes commandes Cargo. Il fournit également une traçabilité SLSA de niveau 3, une liste SBOM et des sorties VEX. Une SBOM recense les composants et les relations de dépendance dans le périmètre défini. La traçabilité enregistre comment et où un artefact a été construit, et fournit des preuves concernant son origine et son processus de construction.
Ce problème présente des répercussions à plus long terme que la technique du « pinning » ne permet pas de résoudre. « arrayref » est une petite bibliothèque utilitaire stable, du genre de celles qui sont écrites une seule fois puis reprises discrètement par des centaines de dépendances pendant une décennie. Peu d’équipes surveillent de près une bibliothèque de ce type, et un très grand nombre d’entre elles finissent par l’intégrer dans leurs produits. C’est ce qui fait de ces bibliothèques des cibles attrayantes, et ce qui laisse les équipes coincées entre une dépendance abandonnée et une réécriture. SecureChain assure la surveillance et l’application des correctifs aux composants pris en charge dans le cadre d’un SLA tant qu’ils sont sous couverture active. Lorsqu’une version atteint son statut de fin de vie, le service « Endless Lifecycle Support » de TuxCare peut continuer à fournir des correctifs de sécurité pour cette version aussi longtemps que vous en dépendez. Pas de mises à niveau forcées, ni de réécriture.
Pourquoi cela va bien au-delà d'une simple campagne
Un écosystème différent, mais le même échec. La campagne « Mastra », menée en juin, a permis d’ajouter une seule ligne de dépendance issue d’un typosquatting à plus de 140 paquets npm « propres ». La vague « Mini Shai-Hulud » a touché 172 paquets npm et deux paquets PyPI tout en conservant une provenance SLSA de niveau 3 valide, tandis que sa suite « Megalodon » a complètement contourné la publication des paquets, injectant en masse des workflows GitHub Actions dans des milliers de dépôts afin de voler des identifiants cloud, des clés SSH et des jetons. Cette campagne transpose le même scénario dans Cargo, en utilisant un compte de mainteneur compromis, une dépendance issue d’un typosquatting et la sémantique « yank » propre à Cargo pour créer une pression à la mise à jour. Wiz a depuis mis en évidence des recoupements au niveau de l’infrastructure entre cette campagne et Mastra, et Mastra a été attribuée à un cluster nord-coréen ; l’écosystème évolue donc, tandis que l’opérateur, lui, reste peut-être le même.
Dans l'ensemble de ces incidents, la surface compromise variait : il pouvait s'agir d'une dépendance transitive, d'un paquet malveillant dont la provenance était vérifiée, ou encore du workflow CI/CD lui-même. L'erreur commune consistait à considérer un éditeur authentifié, une provenance valide ou une compilation réussie comme la preuve que le comportement observé lors de l'importation et de la compilation était inoffensif. Mini Shai-Hulud met en évidence les limites de la provenance, puisque ses artefacts malveillants étaient accompagnés d'une provenance SLSA cryptographiquement valide. Le comportement doit être vérifié dès l'entrée, avant le début de la compilation, plutôt que sur une machine qui détient déjà vos identifiants.
Découvrez SecureChain pour Rust →
FAQ
Un fichier Cargo.lock validé me protège-t-il ?
Pour ce qui est de l’acquisition de la version malveillante, en grande partie oui. Un fichier de verrouillage inchangé maintient un projet sur les versions qu’il a déjà résolues, ce qui, pour la plupart des équipes, correspondait à arrayref 0.3.9, y compris dans un conteneur « clean » qui copie le fichier de verrouillage. Cette protection prend fin dès que vous effectuez une nouvelle résolution, que ce soit via `cargo update`, un bot de dépendances, une modification du manifeste introduisant un nouveau chemin d’accès au crate, une compilation s’exécutant sans le fichier de verrouillage, ou une commande `cargo install` sans l’option `–locked`. De plus, votre fichier de verrouillage n’atteint pas vos propres utilisateurs, puisque seul votre fichier `Cargo.toml` détermine ce qu’ils résolvent. Et rien de tout cela n’affecte une version malveillante déjà présente dans un cache ou une arborescence de dépendances intégrées.
Est-ce que je peux simplement empêcher les scripts de compilation de s'exécuter ?
Ce n'est pas réaliste. Il n'existe aucun indicateur Cargo (stable ou nightly) permettant de contourner les scripts de construction, et la seule exception documentée — qui consiste à remplacer le script de construction d'un paquet déclarant une clé `links` — ne s'applique pas de manière générale. Contrairement aux hooks de `npm install`, les scripts de construction Rust font partie intégrante de la compilation et sont souvent indispensables pour produire un artefact fonctionnel. C'est pourquoi l'inspection doit intervenir en amont de votre construction plutôt que d'être désactivée au sein même de celle-ci.
Le responsable de la maintenance était-il en cause ?
L'équipe de réponse aux incidents de sécurité de Rust a déclaré ne pas penser que l'auteur ait agi de manière malveillante, et que l'explication la plus plausible soit une machine piratée ou des identifiants compromis. Le compte a été verrouillé par mesure de précaution. Il s'agit du même type de faille que lors de l'attaque Mastra, où le point d'entrée était le compte npm d'un ancien contributeur resté inactif pendant environ 16 mois, sans que ses droits d'accès aient jamais été révoqués.
Pourquoi était-il important de supprimer les anciennes versions ?
Le « yanking » ne supprime pas une version, et les fichiers de verrouillage existants continuent d’être générés. Ce qui change, c'est la nouvelle résolution, car le résolveur ignore les versions supprimées à moins qu'elles ne figurent déjà dans le fichier de verrouillage. La suppression des versions correctes a laissé la version malveillante comme étant la version la plus récente que la nouvelle résolution accepterait. Cargo affiche également un avertissement concernant les versions supprimées dès qu'il communique avec l'index ; ainsi, les développeurs qui ont réagi en relançant la résolution avec `cargo update` étaient les plus susceptibles d'aboutir à la version 0.3.10.
Un scanner SBOM ou CVE aurait-il détecté cela ?
Pas en soi. Il n’existait aucun CVE correspondant pendant que les versions malveillantes étaient actives. Un SBOM généré avant la compilation aurait pu mettre en évidence la dépendance inattendue « proc-macro1 », puisqu’elle était déclarée comme une dépendance ordinaire, mais un SBOM est un inventaire plutôt qu’un détecteur de logiciels malveillants. Certains SBOM omettent également les dépendances propres à la compilation, ce qui était le cas des crates du téléchargeur à l’intérieur de « proc-macro1 ». La détection nécessitait tout de même une politique ou une couche d’analyse capable de reconnaître soit la nouvelle dépendance, soit son comportement.
Dans ce contexte, Rust est-il plus ou moins exposé que npm ?
Rust présente des risques différents. Il compte moins de paquets et offre une surface d’attaque plus réduite en nombre, et le respect des règles relatives aux fichiers de verrouillage lors du développement d’applications y contribue. Cependant, les scripts de compilation et les macros proc sont plus difficiles à éviter que les hooks d’installation npm ; ainsi, la méthode simple proposée par npm pour empêcher l’exécution des scripts de cycle de vie n’a pas d’équivalent direct dans Cargo.
Quelle est la solution la plus importante à long terme ?
Transférez la décision relative à la fiabilité vers la phase d'admission. Recompilez et inspectez les paquets avant qu'ils n'entrent dans votre environnement, examinez les différences entre les versions, traitez toute nouvelle dépendance comme un changement ayant une incidence sur la sécurité, et exigez la traçabilité comme preuve de l'origine et du processus de compilation, sans pour autant la considérer comme une garantie que le comportement qui en résulte est inoffensif.