- Après avoir proposé sur LinkedIn un poste de software engineer, l’attaquant a transmis un dépôt GitHub privé déguisé en exercice React/Web3 légitime ; à l’exécution, un malware JavaScript caché dans
tailwind.config.jss’activait sur l’ordinateur du développeur - Ce fichier de configuration de 30 987 octets dissimulait 27 Ko de code obfusqué derrière des milliers d’espaces, récupérait depuis un serveur distant une charge utile de second niveau chiffrée en AES-256-CBC, l’enregistrait dans
%TEMP%\pack, puis l’exécutait avecnode pack - Une observation d’environ 10 minutes dans une VM isolée Windows 11 ARM a montré que la charge utile activait quatre composants : une porte dérobée de contrôle à distance, un voleur de navigateurs et de portefeuilles, un scanner de fichiers récursif et un surveillant du presse-papiers
- Via Socket.IO, le malware contrôlait le terminal, SSH, l’écran, le clavier et la souris, tout en collectant les bases de données Chromium, les stockages d’extensions de portefeuilles, les clés SSH, le code source, les fichiers de configuration, etc. ; 2 588 requêtes ont été observées sur le port d’upload de fichiers
- Tout dépôt d’exercice envoyé par un inconnu doit être traité comme du code non fiable et analysé dans une VM ou un conteneur jetable sans vrai profil de navigateur, clés SSH, identifiants cloud ni portefeuilles ; en cas d’infection, il faut remplacer aussi les secrets après avoir coupé le réseau et préservé les preuves
Un dépôt déguisé en exercice de recrutement Web3 légitime
- L’utilisateur LinkedIn Wayan Adrian a proposé un poste de software engineer chez shrapnel.com et transmis comme exercice le dépôt GitHub privé
tech-active-workplace-frontend-maindu compteyevhen-o - La plateforme de campagne NFT appelée BLAIEXS ressemblait en apparence à un projet React/Web3 ordinaire
- Le frontend React fournissait le branding, un tableau de bord administrateur, la gestion de campagnes et des flux de création de NFT
- L’API Express gérait l’authentification, les données du tableau de bord, les contrôles KYB, la création et la réclamation de NFT, l’upload de fichiers ainsi que certains endpoints factices
- Les scripts de seed créaient des comptes de démonstration superadmin, marque et consommateur, une campagne
Demo NFT Dropet 100 NFTDemo Collectible
- Le vrai périmètre de l’exercice se limitait à une simple fonction d’affichage du réseau MetaMask
- La fonction asynchrone
getChainId()danssrc/utils/ethereum.jsdevait appelereth_chainIdet renvoyer la valeur hexadécimale analysée ounull getNetworkLabel(chainId)dans le même fichier devait retrouver un nom de réseau lisible dans l’objetNETWORKSexistantsrc/components/Wallet/ConnectWalletButton.jsdevait être modifié pour appeler ces deux fonctions après la connexion du portefeuille
- La fonction asynchrone
- Une fois l’implémentation terminée, l’utilisateur a lancé le serveur de développement, mais comme le démarrage tardait anormalement, il a détecté quelque chose d’inhabituel et débranché le câble Internet
Code d’exécution caché dans tailwind.config.js
- Dans
ls -la,tailwind.config.jsfaisait 30 987 octets, mais l’éditeur n’affichait que 97 lignes ;wc -ca confirmé la même taille - Vers la ligne 95, un énorme bloc JavaScript difficile à lire était caché derrière des milliers d’espaces
- Le code utilisait des techniques classiques d’obfuscation JavaScript
- Stockage des chaînes importantes dans un grand tableau
- Rotation du tableau jusqu’à obtenir le checksum attendu
- Masquage de l’accès aux chaînes derrière des fonctions de décodage
- Remplacement des noms explicites par des index numériques et des appels wrapper
- L’un des deux décodeurs reconstituait des chaînes au format Base64, tandis que l’autre appliquait une clé par chaîne et un chiffrement de flux proche de RC4 ; le tableau de chaînes était aussi tourné jusqu’à ce que les index de décodage correspondent
- Sans exécuter le malware, il a été possible de lever l’obfuscation dans l’ordre suivant
- Extraire le tableau de chaînes
- Reproduire la rotation du tableau jusqu’au checksum attendu
- Réimplémenter les fonctions de décodage
- Remplacer des appels comme
b(0x214),c(0x2f1, "key")par les chaînes réelles - Simplifier les objets wrapper et les fonctions auxiliaires pour révéler l’intention d’exécution
Fonctionnement du dropper de premier niveau
- Le code déobfusqué chargeait
child_process,os,fs,path,crypto, puis installaitaxiosetsocket.io-clientdans le répertoire temporaire - Les gestionnaires de processus
uncaughtExceptionetunhandledRejectionétaient configurés pour ignorer les erreurs - Le flux d’exécution de la charge utile distante était le suivant
- L’UID est
59e605dd78fb2aafccd1b622f06a00ca - Les données sont récupérées depuis
http://45.146.252.17/api/service/59e605dd78fb2aafccd1b622f06a00ca - L’UID et la chaîne
saltsont passés àcrypto.scryptSyncpour dériver une clé de 32 octets - La réponse est séparée au format
base64_iv:base64_ciphertext, puis déchiffrée enaes-256-cbc - Le texte en clair est écrit dans le fichier
packdu répertoire temporaire - L’exécution se fait via
child_process.execSync("node pack", { windowsHide: true, cwd: os.tmpdir() })
- L’UID est
- Il s’agit d’un dropper de malware qui télécharge et exécute un autre logiciel malveillant plutôt que d’assurer lui-même la fonctionnalité finale
- Le code téléchargé ne comportait ni vérification de signature, ni liste blanche de hachages, ni épinglage de provenance, ni restriction de chemin, ni garde d’empêchement d’exécution ; l’endpoint distant pouvait donc exécuter du code avec les privilèges du compte système ayant chargé la configuration Tailwind
Analyse dynamique réalisée dans une VM isolée
- Une VM Windows 11 ARM jetable a été configurée avec UTM afin de ne pas exposer le système hôte
- Mémoire
4096 MiB - Disque
64 GiB - Réseau
shared/NAT - Dossiers partagés désactivés
- Mémoire
- Des outils ont été installés pour collecter le comportement du malware sous plusieurs angles
- Wireshark : collecte des paquets et du trafic C2/exfiltration
- Sysinternals Sysmon : télémétrie persistante des événements Windows
- Procmon : collecte de l’activité des processus, du registre et du système de fichiers pendant l’exécution
- Des données leurres ont été placées pour vérifier ce que le malware cherchait à collecter
- Paires de clés SSH
- Dépôts GitHub privés
- Portefeuilles de cryptomonnaies
- Données de navigateur
- Autres fichiers susceptibles d’intéresser un infostealer
yarn starta été exécuté dans la VM, puis le comportement a été observé pendant environ 10 minutes, ce qui a permis de récupérerrun1-full.pcapng,run1-sysmon.evtx,stage2-pack.bin
Chaîne d’infection observée sur le réseau
- Les requêtes HTTP vers
45.146.252.17étaient réparties par rôle selon les ports- Port
80: récupération de la charge utile de premier niveau, enregistrement de l’hôte, envoi des logs - Port
7641: porte dérobée de commande et contrôle Socket.IO - Port
7646: 2 588 uploads de fichiers - Port
7649: 3 uploads de données navigateur, plus volumineux mais moins nombreux
- Port
- La première requête était
GET /api/service/59e605dd78fb2aafccd1b622f06a00ca, avec une réponse au formatbase64_iv:base64_ciphertextetContent-Length: 150897 tailwind.config.jsdéchiffrait la réponse en AES-256-CBC, écrivait le texte en clair dans%TEMP%\pack, puis l’exécutait avecnode pack- Les caractéristiques de la charge utile de second niveau récupérée étaient les suivantes
- SHA-256 :
68a64d8c015c06fd70bcb8c5878c1e430da827dd00b62f8e6ef69e76bb94de5b - Taille :
113136octets - Format : JavaScript ASCII sur une seule ligne
- SHA-256 :
- Le
packdéchiffré était lui aussi à nouveau obfusqué, mais avec les mêmes techniques que la première étape, et a donc pu être levé avec la même procédure
Un payload en deux étapes composé de quatre programmes
packn’était pas un script unique, mais un petit arbre de processus qui lançait les quatre programmes suivants- Écrit et exécute la porte dérobée de commande et contrôle
scdatadans un répertoire temporaire - Écrit et exécute le voleur de navigateur et de portefeuille
ldatadans un répertoire temporaire - Exécute directement en mémoire un scanner de fichiers récursif avec
node -e - Exécute directement en mémoire un surveillant du presse-papiers avec
node -e
- Écrit et exécute la porte dérobée de commande et contrôle
- La configuration principale comprenait l’UID
59e605dd78fb2aafccd1b622f06a00ca, la clé utilisateur308, l’hôte45.146.252.17, le port Socket.IO7641, le port de clé7648, le port d’upload navigateur7649et le port d’upload de fichiers7646
scdata : porte dérobée de contrôle à distance interactive
scdataest écrit sous forme de fichier dans%TEMP%, puis exécuté vianpm i axios socket.io-client ... && node scdata, et reste comme processus enfant de longue durée- Après son exécution, il se configure pour ressembler à un processus légitime
- Titre du processus :
vhost.ctl - Fichier marqueur d’instance unique :
%TEMP%\.npm\vhost.ctl - Enregistrement de l’hôte :
http://45.146.252.17/api/service/… - Envoi des logs :
http://45.146.252.17/api/service/makelog - C2 Socket.IO :
http://45.146.252.17:7641
- Titre du processus :
- Il installe aussi des paquets supplémentaires nécessaires aux fonctions de contrôle à distance
socket.io-client,ssh2,node-pty: terminal et fonctions de type SSHsharp,screenshot-desktop,clipboardy,@nut-tree-fork/nut-js: captures d’écran, presse-papiers, déplacement/clic/défilement de la souris, saisie clavier
- Les événements Socket.IO montrent directement l’étendue du contrôle à distance
- Terminal :
start-terminal,terminal-input,terminal-resize,stop-terminal,command - Vérification de l’hôte et capture :
whour,capture - Contrôle des entrées :
mouseMove,mouseClick,mouseScroll,keyTap,keyCombo - Presse-papiers :
copyText,pasteText - SSH et arrêt :
start_ssh,ssh_input,kill
- Terminal :
- Dans la PCAP, les requêtes de polling et la poignée de main WebSocket sur le port
7641, ainsi que le premier événement serveurwhour, ont été confirmés ; dans Sysmon,node.exe scdata, une requête PowerShell d’informations système et deux groupes d’installations de paquets npm ont été observés successivement - Au-delà du simple vol d’informations, il fournit à l’attaquant des capacités de shell et de contrôle du bureau
ldata : voleur de données de navigateur et de portefeuille
ldataest écrit dans%TEMP%et exécuté vianpm i axios && node ldata; son titre de processus estnpm-cache- Il collecte les profils de navigateur et les stockages d’extensions de portefeuille, puis les envoie vers
http://45.146.252.17:7649/upload, tandis que l’état de LevelDB est vérifié via/cldbs - Les navigateurs ciblés sont largement définis selon le système d’exploitation
- Windows : Chrome, Edge, Brave, LT Browser
- macOS : Chrome, Brave, Opera, LT Browser, Edge et le trousseau de connexion local
- Linux : les dossiers de profil Chromium correspondants
- Depuis les répertoires
DefaultouProfile*, il envoie les bases de données Chromium suivantesLogin DataLogin Data For AccountWeb Data
- Il parcourt ensuite
Local Extension Settings/<extension-id>; les ID d’extensions de portefeuille codés en dur incluent aussinkbihfbeogaeaoehlefnkodbefgpgknnde MetaMask - Pour les huit premiers chemins d’extensions de portefeuille, il copie les répertoires LevelDB dans un dossier temporaire et envoie les fichiers individuellement, puis décide de terminer ou de réessayer selon la réponse
cldbsdu serveur - Les trois uploads confirmés sur le port
7649correspondaient aux bases de données suivantesLogin_Data.sqlite: 51 200 octetsLogin_Data_For_Account.sqlite: 51 200 octetsWeb_Data.sqlite: 262 144 octets
- Les données de test ne contenaient pas de mots de passe enregistrés ni d’entrées de carte, mais incluaient six valeurs d’autoremplissage et des métadonnées du navigateur
- Les navigateurs basés sur Chrome chiffrent localement certains champs, donc la base de données seule ne permet pas toujours de récupérer les secrets en clair
- Toutefois, combinée avec les secrets du système d’exploitation, les cookies, le stockage des extensions et un navigateur déverrouillé, elle devient un élément d’une chaîne de vol d’identifiants
- Il s’arrête une fois les données de navigateur et LevelDB nécessaires envoyées, ou quand le serveur les marque comme complètes, sans attendre de commande supplémentaire de l’opérateur
Scanner de fichiers récursif et surveillant du presse-papiers
- Le scanner de fichiers récursif ne crée pas de fichier séparé et s’exécute avec
node -e, en commençant l’exploration depuis le répertoire personnel de l’utilisateur- Sous Windows, il énumère les lettres de lecteur via
Get-CimInstance Win32_LogicalDisket inspecte aussi la racine de chaque lecteur - Les motifs de fichiers collectés incluent
*.env*,*.md,*.pem,*.ini,*.secret,*.json,*.js,*.ts,*.csv,*.txt,*.doc,*.docx,*.pdf,*.xlsx,.zsh_history,.bash_history ~/.ssh,~/.aws,~/.azure,~/.config,~/.foundrysont automatiquement traités comme dossiers d’intérêt- Il recherche aussi des noms comme
metamask,bitcoin,btc,solana,secret phrase,private key - Les résultats sont envoyés à
http://45.146.252.17:7646/upload
- Sous Windows, il énumère les lettres de lecteur via
- Dans l’environnement leurre,
id_ed25519,id_ed25519.pub,History.txt,seed.js,password.js,tokens.js,ConnectWalletButton.js,ExportWallet.js,RegisterWallet.js,tailwind.config.js,aptos-cli.jsonet d’autres ont effectivement été envoyés - Si
ldatase charge des navigateurs et du stockage des portefeuilles, le scanner de fichiers collecte largement clés SSH, configurations, code source, secrets locaux, historiques shell et fichiers de projet - Le surveillant du presse-papiers s’exécute lui aussi avec
node -e, et son titre de processus estnpm-compiler.log- Il attend quelques secondes puis lit le presse-papiers en boucle
- Sous macOS, il utilise
pbpaste; sous Windows,powershell -NoProfile -NonInteractive Get-Clipboard - Les valeurs modifiées sont envoyées à
http://45.146.252.17/api/service/makelog
- Même sans interpréter le format, il peut capturer tel quel ce que l’utilisateur copie : mots de passe, phrases de récupération, clés privées, jetons API, codes à usage unique, URL GitHub, adresses de portefeuille, secrets de déploiement
Principes à respecter avant d’exécuter un exercice de recrutement
- Un dépôt d’exercice envoyé par une personne inconnue doit être traité comme du code exécutable non fiable tant que sa sûreté n’a pas été vérifiée
- Avant
yarn install,npm install,yarn build,yarn start, il faut examiner les éléments suivantspackage.json- Scripts de cycle de vie
- Fichiers de configuration
- Hooks de dépendances manifestes
- Dans ce cas, le malware se cachait dans
tailwind.config.js, un fichier que beaucoup de gens risquent de négliger ; les fichiers de configuration, les dépendances et les outils de build peuvent eux aussi être exécutés comme du code - Les projets d’entretien doivent être exécutés dans une VM ou un conteneur jetable où aucun vrai secret n’est monté
- Profil de navigateur personnel
- Gestionnaire de mots de passe
- Clés SSH
- Portefeuille de cryptomonnaies
- Jetons GitHub
- Identifiants cloud
- Sessions bancaires
- Compte e-mail principal
- Pour les exercices nécessitant un compte ou un portefeuille, il faut utiliser une nouvelle identité de test sans fonds et jamais réutilisée sur d’autres services
- Pour un exercice Web3, il faut n’utiliser que des testnets et ne jamais connecter un vrai portefeuille à un projet inconnu, même s’il semble inoffensif
Indicateurs pour vérifier une éventuelle infection
- Sur macOS et Linux, vérifier en priorité les éléments suivants
- les processus en cours d’exécution liés à
node,pack,scdata,ldata,npm-compiler,vhost.ctl - les connexions à
45.146.252.17ou aux ports7641,7646,7649 pack,scdata,ldata,vhost.ctldans les répertoires temporaires, Downloads, Desktop, Documents et Library- le fichier marqueur
*/.npm/vhost.ctl - dans le projet téléchargé, les chaînes IP, UID,
/api/service/makelog,node scdata,node ldata,node pack
- les processus en cours d’exécution liés à
- Sous Windows, vérifier les éléments suivants
- parmi les processus
node,npm,cmd,powershell, ceux dont la ligne de commande contientpack,scdata,ldata,node -e, l’adresse IP ouGet-Clipboard - les connexions TCP ouvertes vers
45.146.252.17ou les ports distants7641,7646,7649 - sous
%TEMP%,pack,scdata,ldata,vhost.ctl,.npm\vhost.ctl - les chaînes C2 connues dans le répertoire où le dépôt d’entretien a été cloné
- parmi les processus
- Si un processus Node actif, une connexion à cette IP ou l’un des artefacts
pack·scdata·ldataest détecté, il faut considérer que le système a été exposé - Ces indicateurs sont des points de contrôle initiaux spécifiques aux échantillons analysés et ne constituent pas une procédure forensique complète
Assainissement du système infecté et rotation des secrets
- Commencer par déconnecter l’ordinateur du réseau et éviter de continuer à explorer ou déboguer dans l’environnement infecté, ou d’y copier de nouveaux secrets
- Si des preuves sont nécessaires, conserver les éléments suivants avant suppression
- la liste des processus
- les connexions réseau
- les fichiers suspects
- l’historique du navigateur
- le dépôt malveillant
- Ensuite, arrêter les processus liés à
node pack,node scdata,node ldata,node -e,npm-compiler,vhost.ctl, puis supprimerpack,scdata,ldata,.npm/vhost.ctldans les répertoires temporaires - S’il s’agit d’une VM jetable et qu’il n’est pas nécessaire de préserver un environnement Node légitime, on peut utiliser
pkill nodesur macOS·Linux ou forcer l’arrêt de tous les processusnodesous Windows - Supprimer le dépôt malveillant et
node_modules, mais conserver une copie ZIP hors ligne si une analyse complémentaire est nécessaire - La suppression des fichiers ne suffit pas : il faut aussi remplacer ou révoquer les secrets suivants
- les clés SSH
- les tokens GitHub·npm
- les clés cloud et les clés API
- les secrets de déploiement
- les mots de passe enregistrés dans le navigateur
- les sessions connectées actives
- Si une seed de wallet ou une clé privée a pu être exposée sur le système infecté, il faut créer un nouveau wallet depuis un appareil propre et transférer les fonds
Une attaque qui exploite la frontière de confiance des outils de développement
- Les antivirus traditionnels n’ont pas détecté ce dépôt, et même les agents de code IA populaires utilisés pour résoudre l’exercice n’ont pas identifié le code malveillant caché dans le projet
- Tailwind évalue les fichiers de configuration JavaScript comme des modules Node : l’IIFE de la ligne 95 s’exécute donc dès qu’un outil de développement, un script de build, une intégration éditeur, Tailwind CLI, un bundler ou une tâche CI charge la configuration
- La configuration Tailwind légitime s’arrêtait à la ligne 94, puis environ 27 KB de code obfusqué avaient été ajoutés
- La charge utile téléchargée s’exécute avec les mêmes privilèges système que l’utilisateur qui a chargé la configuration
- elle peut accéder aux fichiers locaux, aux profils de navigateur, aux identifiants enregistrés, aux données d’extensions de wallet, aux clés SSH, aux variables d’environnement, aux tokens de paquets, aux identifiants cloud, au code source et au presse-papiers
- si elle s’exécute en CI, les secrets de déploiement, les artefacts de build, les identifiants de registre et les tokens d’accès à la production peuvent aussi être exposés
- La correction nécessaire consiste à supprimer entièrement le bloc JavaScript obfusqué de
tailwind.config.js
Aucun commentaire pour le moment.