Skip to content

Instantly share code, notes, and snippets.

@GNURub
Created April 1, 2026 06:25
Show Gist options
  • Select an option

  • Save GNURub/9d32e263c23703d49ad3b4d5e4249ff1 to your computer and use it in GitHub Desktop.

Select an option

Save GNURub/9d32e263c23703d49ad3b4d5e4249ff1 to your computer and use it in GitHub Desktop.
Detector script for the WAVESHAPER.V2 / axios npm supply chain attack attributed to North Korea-nexus threat actor UNC1069 (Mandiant/GTIG, March 31 2026). Checks for IOC file hashes, malicious plain-crypto-js package, compromised axios versions (1.14.1 / 0.30.4), active C2 connections (sfrclak.com / 142.11.206.73), npm cache artifacts, suspiciou…
#!/usr/bin/env bash
# =============================================================================
# check_waveshaper.sh — Detector WAVESHAPER.V2 / axios supply chain (UNC1069)
# Basado en: https://cloud.google.com/blog/topics/threat-intelligence/
# north-korea-threat-actor-targets-axios-npm-package/
# Objetivo: Ubuntu / Linux
# Uso: sudo bash check_waveshaper.sh
# =============================================================================
set -euo pipefail
# ---------- colores -----------------------------------------------------------
RED='\033[0;31m'; YEL='\033[1;33m'; GRN='\033[0;32m'
CYA='\033[0;36m'; BLD='\033[1m'; RST='\033[0m'
INFECTED=0
WARNINGS=0
LOG_FILE="/tmp/waveshaper_scan_$(date +%Y%m%d_%H%M%S).log"
log() { echo -e "$*" | tee -a "$LOG_FILE"; }
ok() { log "${GRN} [OK]${RST} $*"; }
warn() { log "${YEL} [WARN]${RST} $*"; ((WARNINGS++)) || true; }
hit() { log "${RED} [!!!]${RST} $*"; ((INFECTED++)) || true; }
info() { log "${CYA} [→]${RST} $*"; }
sep() { log "${BLD}──────────────────────────────────────────────────────${RST}"; }
# =============================================================================
# INDICADORES DE COMPROMISO (IOCs) — Google Threat Intelligence / Mandiant
# =============================================================================
# Hashes SHA256 maliciosos
declare -A MALICIOUS_HASHES=(
["fcb81618bb15edfdedfb638b4c08a2af9cac9ecfa551af135a8402bf980375cf"]="WAVESHAPER.V2 Linux Python RAT"
["e10b1fa84f1d6481625f741b69892780140d4e0e7769e7491e5f4d894c2e0e09"]="SILKBELL dropper (setup.js)"
["58401c195fe0a6204b42f5f90995ece5fab74ce7c69c67a24c61a057325af668"]="plain-crypto-js-4.2.1.tgz"
["92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a"]="WAVESHAPER.V2 macOS Binary"
["617b67a8e1210e4fc87c92d1d1da45a2f311c08d26e89b12307cf583c900d101"]="WAVESHAPER.V2 Windows Stage 1"
)
# IPs y dominio C2
C2_IPS=("142.11.206.73" "23.254.167.216")
C2_DOMAIN="sfrclak.com"
C2_PORT="8000"
# Paquete malicioso
MALICIOUS_PKG="plain-crypto-js"
MALICIOUS_PKG_VERSIONS=("4.2.0" "4.2.1")
# Versiones comprometidas de axios
AXIOS_BAD_VERSIONS=("1.14.1" "0.30.4")
# Archivo backdoor en Linux
LINUX_RAT="/tmp/ld.py"
# User-Agent del beacon C2
C2_USERAGENT="mozilla/4.0 (compatible; msie 8.0; windows nt 5.1; trident/4.0)"
# =============================================================================
clear
log ""
log "${BLD}╔══════════════════════════════════════════════════════════╗${RST}"
log "${BLD}║ WAVESHAPER.V2 / axios Supply Chain — Detector Linux ║${RST}"
log "${BLD}║ Amenaza: UNC1069 (Corea del Norte) — GTIG / Mandiant ║${RST}"
log "${BLD}╚══════════════════════════════════════════════════════════╝${RST}"
log ""
info "Log guardado en: $LOG_FILE"
info "Inicio: $(date)"
sep
# =============================================================================
# 1. ARCHIVO BACKDOOR PRINCIPAL (/tmp/ld.py)
# =============================================================================
log ""
log "${BLD}[1/8] Backdoor Python en /tmp/ld.py${RST}"
sep
if [[ -f "$LINUX_RAT" ]]; then
hit "ENCONTRADO: $LINUX_RAT"
HASH=$(sha256sum "$LINUX_RAT" | awk '{print $1}')
if [[ "${MALICIOUS_HASHES[$HASH]+_}" ]]; then
hit "SHA256 COINCIDE con IOC conocido: ${MALICIOUS_HASHES[$HASH]}"
hit "Hash: $HASH"
else
warn "Archivo existe pero hash no coincide exactamente ($HASH)"
warn "Puede ser una variante. Analizar manualmente."
fi
else
ok "$LINUX_RAT no encontrado"
fi
# Búsqueda adicional de ld.py en /tmp y variantes
for f in /tmp/ld.py /tmp/ld2.py /tmp/.ld.py /tmp/ldd.py; do
[[ "$f" == "$LINUX_RAT" ]] && continue
if [[ -f "$f" ]]; then
warn "Archivo sospechoso adicional encontrado: $f"
fi
done
# =============================================================================
# 2. ARCHIVOS CON HASHES IOC
# =============================================================================
log ""
log "${BLD}[2/8] Búsqueda de archivos con hashes maliciosos conocidos${RST}"
sep
SEARCH_DIRS=("/tmp" "/var/tmp" "/home" "/root" "/opt" "/srv")
FOUND_FILES=()
for dir in "${SEARCH_DIRS[@]}"; do
if [[ -d "$dir" ]]; then
while IFS= read -r -d '' file; do
HASH=$(sha256sum "$file" 2>/dev/null | awk '{print $1}' || true)
if [[ -n "$HASH" && "${MALICIOUS_HASHES[$HASH]+_}" ]]; then
hit "MATCH IOC: $file${MALICIOUS_HASHES[$HASH]}"
hit "Hash: $HASH"
FOUND_FILES+=("$file")
fi
done < <(find "$dir" -maxdepth 5 -type f -print0 2>/dev/null)
fi
done
# Buscar setup.js con nombre sospechoso
while IFS= read -r -d '' file; do
HASH=$(sha256sum "$file" 2>/dev/null | awk '{print $1}' || true)
if [[ -n "$HASH" && "${MALICIOUS_HASHES[$HASH]+_}" ]]; then
hit "MATCH IOC (setup.js): $file${MALICIOUS_HASHES[$HASH]}"
FOUND_FILES+=("$file")
fi
done < <(find /tmp /home /root /opt -name "setup.js" -print0 2>/dev/null)
if [[ ${#FOUND_FILES[@]} -eq 0 ]]; then
ok "No se encontraron archivos con hashes IOC conocidos"
fi
# =============================================================================
# 3. PAQUETE MALICIOSO EN npm (plain-crypto-js)
# =============================================================================
log ""
log "${BLD}[3/8] Paquete npm malicioso: plain-crypto-js${RST}"
sep
# Buscar en node_modules de todo el sistema
info "Buscando plain-crypto-js en node_modules..."
FOUND_MODULES=()
while IFS= read -r -d '' dir; do
PKG_JSON="$dir/package.json"
if [[ -f "$PKG_JSON" ]]; then
PKG_VERSION=$(python3 -c "import json,sys; d=json.load(open('$PKG_JSON')); print(d.get('version','?'))" 2>/dev/null || true)
hit "PAQUETE MALICIOSO encontrado: $dir (versión: $PKG_VERSION)"
FOUND_MODULES+=("$dir")
fi
done < <(find / -path "*/node_modules/plain-crypto-js" -type d -print0 2>/dev/null)
if [[ ${#FOUND_MODULES[@]} -eq 0 ]]; then
ok "plain-crypto-js no encontrado en node_modules"
fi
# Buscar en package-lock.json y yarn.lock
info "Buscando plain-crypto-js en lockfiles..."
LOCKFILE_HITS=()
while IFS= read -r file; do
if grep -q "plain-crypto-js" "$file" 2>/dev/null; then
hit "plain-crypto-js referenciado en: $file"
grep -n "plain-crypto-js" "$file" | while read -r line; do
hit "$line"
done
LOCKFILE_HITS+=("$file")
fi
done < <(find /home /root /opt /srv /var/www -name "package-lock.json" -o -name "yarn.lock" -o -name "pnpm-lock.yaml" 2>/dev/null)
if [[ ${#LOCKFILE_HITS[@]} -eq 0 ]]; then
ok "plain-crypto-js no encontrado en lockfiles"
fi
# =============================================================================
# 4. VERSIONES COMPROMETIDAS DE AXIOS
# =============================================================================
log ""
log "${BLD}[4/8] Versiones comprometidas de axios (1.14.1 / 0.30.4)${RST}"
sep
AXIOS_HITS=()
while IFS= read -r -d '' dir; do
PKG_JSON="$dir/package.json"
if [[ -f "$PKG_JSON" ]]; then
PKG_VERSION=$(python3 -c "import json,sys; d=json.load(open('$PKG_JSON')); print(d.get('version',''))" 2>/dev/null || true)
for bad_ver in "${AXIOS_BAD_VERSIONS[@]}"; do
if [[ "$PKG_VERSION" == "$bad_ver" ]]; then
hit "axios versión COMPROMETIDA ($bad_ver) en: $dir"
AXIOS_HITS+=("$dir")
fi
done
fi
done < <(find / -path "*/node_modules/axios" -type d -print0 2>/dev/null)
if [[ ${#AXIOS_HITS[@]} -eq 0 ]]; then
ok "No se detectaron versiones comprometidas de axios"
else
warn "ACCIÓN REQUERIDA: Degradar axios a 1.14.0 o anterior / 0.30.3 o anterior"
fi
# =============================================================================
# 5. CACHÉ NPM CONTAMINADA
# =============================================================================
log ""
log "${BLD}[5/8] Caché npm/yarn/pnpm contaminada${RST}"
sep
NPM_CACHE_DIRS=()
# npm
for user_home in /root /home/*; do
cache_dir="$user_home/.npm"
[[ -d "$cache_dir" ]] && NPM_CACHE_DIRS+=("$cache_dir")
done
# yarn
for user_home in /root /home/*; do
cache_dir="$user_home/.yarn/cache"
[[ -d "$cache_dir" ]] && NPM_CACHE_DIRS+=("$cache_dir")
done
CACHE_HITS=()
for cache_dir in "${NPM_CACHE_DIRS[@]}"; do
info "Inspeccionando caché: $cache_dir"
# Buscar el tgz malicioso por hash
while IFS= read -r -d '' f; do
HASH=$(sha256sum "$f" 2>/dev/null | awk '{print $1}' || true)
if [[ "${MALICIOUS_HASHES[$HASH]+_}" ]]; then
hit "ARCHIVO MALICIOSO en caché: $f"
hit "Tipo: ${MALICIOUS_HASHES[$HASH]}"
CACHE_HITS+=("$f")
fi
done < <(find "$cache_dir" -type f -print0 2>/dev/null)
# Buscar por nombre
while IFS= read -r f; do
hit "plain-crypto-js encontrado en caché: $f"
CACHE_HITS+=("$f")
done < <(find "$cache_dir" -name "*plain-crypto*" 2>/dev/null)
done
if [[ ${#CACHE_HITS[@]} -eq 0 ]]; then
ok "No se encontraron artefactos maliciosos en caché npm/yarn"
else
warn "ACCIÓN: Limpiar caché con: npm cache clean --force && yarn cache clean"
fi
# =============================================================================
# 6. CONEXIONES DE RED AL C2
# =============================================================================
log ""
log "${BLD}[6/8] Conexiones de red al C2 (sfrclak.com / 142.11.206.73)${RST}"
sep
NET_HITS=0
# Comprobar conexiones activas
if command -v ss &>/dev/null; then
for ip in "${C2_IPS[@]}"; do
CONNS=$(ss -tnp 2>/dev/null | grep "$ip" || true)
if [[ -n "$CONNS" ]]; then
hit "CONEXIÓN ACTIVA al C2 ($ip):"
echo "$CONNS" | while read -r line; do hit " $line"; done
((NET_HITS++)) || true
fi
done
DOMAIN_CONNS=$(ss -tnp 2>/dev/null | grep ":$C2_PORT" || true)
if [[ -n "$DOMAIN_CONNS" ]]; then
warn "Conexiones activas al puerto $C2_PORT (puerto C2):"
echo "$DOMAIN_CONNS" | while read -r line; do warn " $line"; done
fi
elif command -v netstat &>/dev/null; then
for ip in "${C2_IPS[@]}"; do
CONNS=$(netstat -tnp 2>/dev/null | grep "$ip" || true)
if [[ -n "$CONNS" ]]; then
hit "CONEXIÓN ACTIVA al C2 ($ip):"
echo "$CONNS" | while read -r line; do hit " $line"; done
((NET_HITS++)) || true
fi
done
fi
# Comprobar resolución DNS del dominio C2
if command -v host &>/dev/null; then
RESOLVED=$(host "$C2_DOMAIN" 2>/dev/null || true)
if echo "$RESOLVED" | grep -q "142.11.206.73"; then
warn "El dominio C2 ($C2_DOMAIN) resuelve a la IP maliciosa conocida"
else
ok "El dominio C2 ($C2_DOMAIN) no resuelve a IP conocida (puede estar bloqueado)"
fi
fi
# Comprobar /etc/hosts por si hay entradas C2
if grep -qE "(sfrclak|142\.11\.206\.73|23\.254\.167\.216)" /etc/hosts 2>/dev/null; then
warn "/etc/hosts contiene referencias al C2 — revisar manualmente"
grep -E "(sfrclak|142\.11\.206\.73|23\.254\.167\.216)" /etc/hosts | while read -r line; do
warn " /etc/hosts: $line"
done
else
ok "/etc/hosts limpio"
fi
# Revisar logs de conexiones pasadas (si existen)
for log_path in /var/log/syslog /var/log/auth.log /var/log/kern.log; do
if [[ -f "$log_path" ]]; then
PAST=$(grep -E "(sfrclak|142\.11\.206\.73|23\.254\.167\.216)" "$log_path" 2>/dev/null | tail -5 || true)
if [[ -n "$PAST" ]]; then
hit "Referencias al C2 encontradas en $log_path:"
echo "$PAST" | while read -r line; do hit " $line"; done
fi
fi
done
[[ $NET_HITS -eq 0 ]] && ok "No se detectaron conexiones activas al C2"
# =============================================================================
# 7. PROCESOS SOSPECHOSOS
# =============================================================================
log ""
log "${BLD}[7/8] Procesos sospechosos en ejecución${RST}"
sep
PROC_HITS=0
# Python ejecutando ld.py o scripts de /tmp
SUSPICIOUS_PROCS=$(ps aux 2>/dev/null | grep -E "(python.*ld\.py|python.*/tmp/|node.*setup\.js)" | grep -v grep || true)
if [[ -n "$SUSPICIOUS_PROCS" ]]; then
hit "PROCESO SOSPECHOSO detectado:"
echo "$SUSPICIOUS_PROCS" | while read -r line; do hit " $line"; done
((PROC_HITS++)) || true
fi
# Procesos con conexión al puerto 8000 (beacon C2)
if command -v lsof &>/dev/null; then
BEACON_PROCS=$(lsof -i ":$C2_PORT" 2>/dev/null | grep -v "^COMMAND" || true)
if [[ -n "$BEACON_PROCS" ]]; then
warn "Proceso usando puerto $C2_PORT (puerto beacon C2):"
echo "$BEACON_PROCS" | while read -r line; do warn " $line"; done
fi
fi
# Procesos node spawnando curl/python hacia /tmp
NODE_CURL=$(ps aux 2>/dev/null | grep -E "node.*curl|node.*python" | grep -v grep || true)
if [[ -n "$NODE_CURL" ]]; then
warn "Node.js spawnando curl/python (patrón sospechoso):"
echo "$NODE_CURL" | while read -r line; do warn " $line"; done
fi
[[ $PROC_HITS -eq 0 ]] && ok "No se encontraron procesos sospechosos activos"
# =============================================================================
# 8. REVISIÓN POSTINSTALL EN package.json
# =============================================================================
log ""
log "${BLD}[8/8] Hooks postinstall sospechosos en package.json${RST}"
sep
POSTINSTALL_HITS=()
while IFS= read -r file; do
# Buscar "postinstall" que ejecute node setup.js u otros comandos sospechosos
if grep -qE '"postinstall".*"node setup\.js"' "$file" 2>/dev/null; then
hit "Hook postinstall malicioso en: $file"
grep -n "postinstall" "$file" | while read -r line; do hit " $line"; done
POSTINSTALL_HITS+=("$file")
fi
done < <(find / -path "*/node_modules/plain-crypto-js/package.json" 2>/dev/null)
find /tmp /home /root /opt -name "package.json" 2>/dev/null | while read -r file; do
if grep -qP '"postinstall"\s*:\s*"node setup\.js"' "$file" 2>/dev/null; then
hit "Hook postinstall sospechoso en: $file"
POSTINSTALL_HITS+=("$file")
fi
done
if [[ ${#POSTINSTALL_HITS[@]} -eq 0 ]]; then
ok "No se detectaron hooks postinstall maliciosos"
fi
# =============================================================================
# RESUMEN FINAL
# =============================================================================
sep
log ""
log "${BLD}╔══════════════════════════════════╗${RST}"
log "${BLD}║ RESULTADO DEL ANÁLISIS ║${RST}"
log "${BLD}╚══════════════════════════════════╝${RST}"
log ""
if [[ $INFECTED -gt 0 ]]; then
log "${RED}${BLD} ⚠ SISTEMA POSIBLEMENTE COMPROMETIDO${RST}"
log "${RED} $INFECTED indicadores de compromiso detectados${RST}"
log ""
log "${BLD} PASOS INMEDIATOS:${RST}"
log " 1. Aislar la máquina de la red"
log " 2. Eliminar /tmp/ld.py si existe: ${RED}rm -f /tmp/ld.py${RST}"
log " 3. Bloquear C2 en firewall:"
log " ${YEL}sudo iptables -A OUTPUT -d 142.11.206.73 -j DROP${RST}"
log " ${YEL}sudo iptables -A OUTPUT -d 23.254.167.216 -j DROP${RST}"
log " 4. Limpiar caché npm: ${YEL}npm cache clean --force${RST}"
log " 5. Degradar axios a versión segura (≤1.14.0 / ≤0.30.3)"
log " 6. Rotar TODOS los tokens, API keys y credenciales del sistema"
log " 7. Revisar cron jobs: ${YEL}crontab -l && cat /etc/cron*/${RST}"
elif [[ $WARNINGS -gt 0 ]]; then
log "${YEL}${BLD} ⚡ ADVERTENCIAS — Revisar manualmente${RST}"
log "${YEL} $WARNINGS avisos detectados (sin IOCs directos confirmados)${RST}"
log ""
log " Revisa los puntos marcados con [WARN] en este log."
else
log "${GRN}${BLD} ✓ SISTEMA LIMPIO${RST}"
log "${GRN} No se detectaron IOCs del ataque WAVESHAPER.V2 / axios${RST}"
log ""
log " ${BLD}Recomendaciones preventivas:${RST}"
log " • Asegúrate de usar axios ≤ 1.14.0 o ≤ 0.30.3 en todos tus proyectos"
log " • Añade a tu firewall: iptables -A OUTPUT -d 142.11.206.73 -j DROP"
log " • Usa 'npm audit' regularmente en tus proyectos Node.js"
fi
log ""
log " Log completo guardado en: ${CYA}$LOG_FILE${RST}"
log " Fecha: $(date)"
sep
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment