Version 2.0 del detector
This commit is contained in:
parent
6b9d5201bd
commit
6c92cc4dba
106
README.md
106
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.
|
||||
============================================================
|
||||
|
||||
```
|
||||
|
||||
|
||||
124
shai_hulud_detector.py
Executable file
124
shai_hulud_detector.py
Executable file
@ -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())
|
||||
Loading…
x
Reference in New Issue
Block a user