diff --git a/README.md b/README.md index e69de29..78da82e 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,106 @@ +# Detección de Shai-Hulud + +He creado un script en Python para la detección en tus repositorios de código. +Además te incluyo el protocolo de respuesta ante una infección confirmada. + +## 1. Explicación del Script de Python (shai_hulud_detector.py) + +El script está diseñado para realizar una auditoría estática y dinámica en tres niveles sobre cualquier proyecto o repositorio Node.js dentro del directorio de trabajo actual: +Nivel 1: Detección de Droppers Físicos (Análisis Estático) + + ¿Qué busca?: Archivos con nombres clave que utiliza el malware (setup.mjs, math_init.js, Math_Symbol.js). + ¿Cómo lo hace?: Escanea recursivamente el árbol de archivos. Si encuentra un paquete susceptible en node_modules (como flat-cache o file-entry-cache), comprueba si dentro de su carpeta existen dichos archivos dropper. Si la dependencia está presente pero no contiene los droppers, la marca como LIMPIA [OK]. + +Nivel 2: Inspección de Configuración (package.json) + + Hooks de ciclo de vida (preinstall): Analiza si el campo "scripts" de algún package.json intenta ejecutar comandos sospechosos (por ejemplo, "preinstall": "node setup.mjs"). + Versiones infectadas conocidas: Contrasta las versiones declaradas e instaladas con la lista negra de publicaciones maliciosas confirmadas (keyv@6.0.0, cacheable@2.5.1, cache-manager@7.2.10, etc.). + +Nivel 3: Auditoría del Árbol de Dependencias (Análisis Dinámico) + + Equivalente a npm list: Identifica las raíces de los proyectos Node.js (donde hay un package-lock.json o node_modules) y ejecuta automáticamente una inspección del árbol de dependencias resueltas para las familias de riesgo (keyv, cacheable, flat-cache, file-entry-cache, etc.). + Esto permite visibilizar la cadena de dependencias transitivas (por ejemplo, ver si llegan a través de eslint). + +## 2. Procedimiento de Actuación en Caso de Infección Confirmada + +Si el script o tus revisiones confirman la presencia de un dropper o de una versión infectada, se debe asumir que el entorno ha sido comprometido y aplicar inmediatamente el siguiente protocolo: +Paso 1: Aislamiento del Entorno + + Detener procesos activos: Cancela inmediatamente cualquier servidor, pipeline de CI/CD o proceso de construcción (npm run build, npm start) que se esté ejecutando en ese equipo. + Desconectar de la red: Si es una máquina o servidor en producción, aíslala temporalmente de la red para evitar que la carga útil descargue el entorno Bun o intente exfiltrar credenciales. + +Paso 2: Revocación Inmediata de Credenciales (Crítico) + +Como el objetivo principal de Shai-Hulud es el robo y reutilización de credenciales: + + Revocar Tokens de NPM: Entra en tu cuenta de npmjs.com y revoca todos los Access Tokens activos inmediatamente. + Revocar Tokens de GitHub / CI/CD: Revoca cualquier Personal Access Token (PAT), clave SSH o credenciales de integración de CI/CD (GitHub Actions, Gitea, etc.) que estuvieran almacenadas en la máquina infectada (~/.npmrc, ~/.ssh, variables de entorno). + +Paso 3: Limpieza y Eliminación + +Limpiar la caché global de npm: + +npm cache clean --force + +Borrar directorios y bloqueos contaminados: Elimina las carpetas node_modules y el archivo package-lock.json del proyecto afectado: + +rm -rf node_modules package-lock.json + +Paso 4: Reinstalación Segura + + Fijar versiones seguras: Asegúrate en tu package.json de que las dependencias estén apuntando a versiones anteriores estables o a las versiones parcheadas que hayan publicado los mantenedores oficiales. + +Desactivar scripts automáticos: Fuerza la instalación sin ejecutar los hooks de ciclo de vida para evitar que cualquier dependencia infectada vuelva a dispararse: + +npm install --ignore-scripts + +Paso 5: Auditar Publicaciones (Si eres mantenedor) + +Si tenías permisos de publicación en algún paquete de npm en esa máquina: + + Revisa tu perfil en el registro de npm para comprobar si el gusano publicó versiones no autorizadas (versiones parche como x.x.+1) a tu nombre el 4 de agosto o en fechas posteriores. + Despublica (unpublish) o marca como deprecadas (deprecate) de inmediato las versiones maliciosas generadas en tus paquetes. + +## Script de Detección + +Puedes usar el fichero `shai_hulud_detector.py` se trata de la segunda versión en la que he ampliado la revision del análisis. + +Simplemente dale permisos de ejecución: + +``` +chmod +x shai_hulud_detector.py` +``` + +Para detectarlo simplemente invocalo desde la ruta en que desees se analicen todos los directorios y subdirectorios: + +``` +cd ~/mi-repositorio-de-codigo +/ruta-detector/shai_hulud_detector.py +``` + +Ejemplo de salida: + +``` +$ shai_hulud_detector.py + +... + +[+] Ejecutando auditoría del árbol de dependencias en: /home/soloconlinux/repo/drawdb/node_modules/url +--- Árbol de dependencias resuelto --- +url@0.11.1 /home/soloconlinux/repo/drawdb/node_modules/url +└── (empty) +-------------------------------------- + +[+] Ejecutando auditoría del árbol de dependencias en: /home/soloconlinux/repo/drawdb/node_modules/tailwindcss +--- Árbol de dependencias resuelto --- +tailwindcss@3.3.6 /home/soloconlinux/repo/drawdb/node_modules/tailwindcss +└── (empty) +-------------------------------------- + +============================================================ +[OK] Escaneo finalizado: No se detectaron archivos dropper ni versiones maliciosas. +============================================================ + +``` + + diff --git a/shai_hulud_detector.py b/shai_hulud_detector.py new file mode 100755 index 0000000..149a096 --- /dev/null +++ b/shai_hulud_detector.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +import os +import json +import subprocess + +# Indicadores de Compromiso (IoCs): Archivos dropper usados por el gusano +DROPPER_FILES = ["setup.mjs", "math_init.js", "Math_Symbol.js"] + +# Paquetes a auditar y sus versiones estrictamente maliciosas confirmadas +KNOWN_VULNERABLE_PACKAGES = { + "keyv": ["6.0.0"], + "cacheable": ["2.5.1"], + "@cacheable/memory": ["2.2.1"], + "@cacheable/net": ["2.1.1"], + "@cacheable/node-cache": ["3.1.2"], + "@cacheable/utils": ["2.5.1"], + "cache-manager": ["7.2.10"], + "cacheable-request": ["13.0.20"] +} + +TARGET_PACKAGES = [ + "keyv", "cacheable", "cacheable-request", "cache-manager", + "flat-cache", "file-entry-cache" +] + +def check_dropper_files(package_dir): + """Inspecciona el directorio de un paquete en busca de archivos dropper.""" + found_droppers = [] + for dropper in DROPPER_FILES: + path = os.path.join(package_dir, dropper) + if os.path.exists(path): + found_droppers.append(path) + return found_droppers + +def run_npm_list_audit(project_dir): + """Ejecuta una inspección de dependencias equivalente a 'npm list'.""" + print(f"\n[+] Ejecutando auditoría del árbol de dependencias en: {project_dir}") + pkgs_str = " ".join(TARGET_PACKAGES) + try: + result = subprocess.run( + f"npm list {pkgs_str}", + shell=True, + cwd=project_dir, + capture_output=True, + text=True + ) + output = result.stdout.strip() or result.stderr.strip() + print("--- Árbol de dependencias resuelto ---") + print(output) + print("--------------------------------------") + except Exception as e: + print(f"[!] No se pudo ejecutar npm list: {e}") + +def scan_directory(root_dir): + print(f"[*] Iniciando escaneo avanzado contra 'Shai-Hulud' desde: {os.path.abspath(root_dir)}\n") + warnings_found = 0 + projects_with_npm = set() + + for dirpath, dirnames, filenames in os.walk(root_dir): + if ".git" in dirnames: + dirnames.remove(".git") + + # 1. Comprobación directa de archivos dropper en la ruta actual + for file in filenames: + if file in DROPPER_FILES: + file_path = os.path.join(dirpath, file) + print(f"[CRÍTICO] Archivo dropper malicioso detectado directamente en: {file_path}") + warnings_found += 1 + + # 2. Análisis de package.json + if "package.json" in filenames: + pkg_path = os.path.join(dirpath, "package.json") + + # Registrar si es la raíz de un proyecto de Node.js + if "node_modules" in dirnames or os.path.exists(os.path.join(dirpath, "package-lock.json")): + projects_with_npm.add(dirpath) + + try: + with open(pkg_path, "r", encoding="utf-8") as f: + data = json.load(f) + + # Verificar scripts preinstall sospechosos + scripts = data.get("scripts", {}) + preinstall = scripts.get("preinstall", "") + if any(dropper in preinstall for dropper in DROPPER_FILES): + print(f"[CRÍTICO] Hook preinstall infectado en: {pkg_path}") + print(f" Comando: \"preinstall\": \"{preinstall}\"") + warnings_found += 1 + + # Inspección de instalación local si este package.json pertenece a un nodo instalado + pkg_name = data.get("name", "") + if pkg_name in TARGET_PACKAGES or any(pkg_name.startswith(p) for p in ["@cacheable/"]): + # Verificar si este paquete en node_modules contiene un dropper físicamente + droppers = check_dropper_files(dirpath) + if droppers: + print(f"[CRÍTICO] ¡ALERTA CONFIRMADA! Dropper hallado dentro del paquete '{pkg_name}':") + for d in droppers: + print(f" -> {d}") + warnings_found += 1 + else: + version = data.get("version", "desconocida") + # Verificar versión contra la lista de infecciones confirmadas + if pkg_name in KNOWN_VULNERABLE_PACKAGES and version in KNOWN_VULNERABLE_PACKAGES[pkg_name]: + print(f"[ALERTA] Versión infectada conocida hallada: {pkg_name}@{version} en {dirpath}") + warnings_found += 1 + else: + print(f"[OK] Paquete de riesgo presente pero LIMPIO (Sin droppers, versión segura): {pkg_name}@{version}") + + except (json.JSONDecodeError, PermissionError): + continue + + # 3. Ejecutar la comprobación automática del árbol en los proyectos encontrados + for proj in projects_with_npm: + run_npm_list_audit(proj) + + print("\n" + "="*60) + if warnings_found == 0: + print("[OK] Escaneo finalizado: No se detectaron archivos dropper ni versiones maliciosas.") + else: + print(f"[RESULTADO] Se encontraron {warnings_found} amenazas reales/críticas. Revisa los detalles arriba.") + print("="*60) + +if __name__ == "__main__": + scan_directory(os.getcwd())