Shai-Hulud_DETECTOR/shai_hulud_detector.py

125 lines
5.1 KiB
Python
Executable File

#!/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())