125 lines
5.1 KiB
Python
Executable File
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())
|