DevOps

Alerta Sysadmins: Malware Go Distribuido Vía Módulos Terraform y Go

TL;DR — Lo que vas a tener al final

  • Tiempo de setup: 30 minutos para instalación, 1-2 horas para upgrade in-place.
  • Requisitos: Backup de /home, conexión a internet, 20+ GB libres.
  • Resultado: Sistema actualizado con soporte extendido, drivers funcionando.
  • Troubleshooting: dnf distro-sync, akmods para NVIDIA, journalctl -p err.

Por qué importa este caso

El 22 de septiembre de 2026, los investigadores de Aikido Security publicaron un descubrimiento que reordena las prioridades de cualquier equipo DevOps que use Terraform: por primera vez se documenta malware distribuido de forma sistemática a través del registro oficial de proveedores de HashiCorp. Cuatro paquetes maliciosos se hicieron pasar por piezas legítimas de infraestructura como código durante semanas, dos de ellos imitando al popular proveedor kreuzwerker/docker con un solo carácter cambiado (kreuzwenker/docker, con n en lugar de r).

Para sysadmins y DevOps juniors en LATAM que mantienen 1-5 servidores en homelab o producción pequeña, el riesgo es concreto: si tu laptop corre terraform init con credenciales de AWS, Azure o Google Cloud, un proveedor malicioso se ejecuta con esos mismos permisos. Y el paquete no se despierta solo: espera una combinación exacta de containerName + networkID cuyo SHA256 coincida con un valor hardcodeado. Es un ataque dirigido, no masivo, lo que hace más difícil detectarlo en sandboxes genéricos.

El origen: Graphalgo, una campaña que mutó

El malware encontrado en Terraform no es nuevo: es una variante Go de la familia Graphalgo, atribuida por ReversingLabs en febrero de 2026 a actores de amenaza norcoreanos. La versión original se distribuía por paquetes npm maliciosos insertados en repositorios de “tareas de coding” que reclutadores falsos pedían a desarrolladores de Web3. En abril de 2026, ReversingLabs documentó un respawn del mismo actor. En agosto-septiembre de 2026, la misma clave pública, los mismos espacios de Slack y el mismo contrato inteligente reaparecieron, pero esta vez apuntando al ecosistema Go y al registro de Terraform.

La pista que une los hitos es técnica: la clave pública 302a300506032b656e032100bad013df6eec5d686f4cc8551e0a5c87a0135164bdd1dafb1c75141d1b526702 aparece tanto en muestras npm de abril como en los nuevos binarios Go. Lo mismo ocurre con dos workspaces de Slack (portfolio-devs.slack.com y portfolio-testers.slack.com) y un contrato Ethereum desplegado en Arbitrum Sepolia (0xAD02b5cDE693529d3bdA0266299501ad0193036C). Cuando tres infraestructuras independientes convergen en las mismas muestras, es la misma operación.

Cómo funciona la infección en Terraform

El ataque en Terraform se apoya en tres trucos que vale la pena entender uno por uno. La tabla resume el paquete, el vector de activación y el efecto sobre la máquina infectada:

Paquete malicioso Trigger de activación Qué se ejecuta
gocommunity-io/dockerd SHA256(containerName+networkID) == b9966e3762e9a0d5... go run . detached sobre import-resource.sqlite3
kreuzwenker/docker Idéntico al anterior Idéntico, mismo ZIP cifrado
gocommunity.io/orderedbtree Sin trigger (texto plano) RAT de segunda etapa directo en el binario
gogets.dev/btreex Objeto con precio entero específico go run . sobre btreex.sql

El detalle técnico de cada uno:

Trigger pasivo basado en SHA256

Ambos proveedores maliciosos, gocommunity-io/dockerd (222 descargas) y kreuzwenker/docker (1.449 descargas), tienen un entry point oculto en /internal/provider/resource_docker_container_funcs.go. El código no se activa siempre: calcula SHA256 de la concatenación containerName + networkID y lo compara con b9966e3762e9a0d5d263b8cb3cca07294f81af9714d40ddf4628cb85d74e8ad5. Solo si el hash coincide, el malware continúa. Para cualquier otro input, el proveedor se comporta como uno legítimo. Esto es lo que permite que pase sandboxes que ejecutan el paquete con variables aleatorias: nunca se despierta.

Entrega cifrada con AES

Una vez que el trigger se cumple, el hash SHA256 se reutiliza como clave AES. Con esa clave descifra la ruta de un archivo dentro del propio paquete: examples/resources/docker_container/import-resource.sqlite3 (SHA256 5f892a5424e88a21a3eb3d7f82ebf04d8ac31cdb19ada25153be4165df977d0f). El archivo parece un dump de SQLite pero es un ZIP cifrado: lo descomprime, descifra cada entry, y ejecuta el resultado con un go run . detached. Esa segunda etapa es la que hace el trabajo sucio, en un proceso separado que sobrevive al proveedor.

Doble canal de comando y control

El RAT de segunda etapa no usa un servidor C2 tradicional. Tiene dos canales paralelos:

  • Slack: postea un “System Report” en el canal frontend-devs del workspace portfolio-devs.slack.com con plataforma, arquitectura, hostname, usuario y disponibilidad de node. Después migra comandos al workspace portfolio-testers.slack.com, canal qa-announcements, cifrados con ECDH entre una clave efímera del malware y la clave pública del actor.
  • Blockchain: cada 3 segundos lee serviceData1 y serviceData2 del contrato 0xAD02b5cDE693529d3bdA0266299501ad0193036C en Arbitrum Sepolia. Cada 10 segundos consulta Slack. Los mensajes cifrados que recibe pueden ordenarle ejecutar Go o JavaScript adicional, o autoeliminarse.

El uso de Slack es un intento deliberado de camuflarse en tráfico corporativo normal. El contrato inteligente es un canal de respaldo silencioso: HTTP a nodos RPC públicos no levanta alertas en la mayoría de las herramientas de egress filtering. La recomendación de Aikido, replicada por Purple Shield Security, es bloquear o alertar sobre tráfico HTTP a servicios blockchain desde máquinas que no tengan razón legítima para hablar con Ethereum.

Los cuatro paquetes a buscar en tu repositorio

La acción concreta que cualquier sysadmin puede hacer hoy es buscar los cuatro nombres del paquete en sus dependencias:

# Buscar en archivos Terraform (.tf y .terraform.lock.hcl)
grep -rn --include='*.tf' --include='*.hcl' -E 'gocommunity-io/dockerd|kreuzwenker/docker' .
# Buscar en go.mod y go.sum
grep -nE 'gocommunity.io/orderedbtree|gogets.dev/btreex' go.mod go.sum
# Buscar en caches de CI runners y laptops de contratistas
find / -type f ( -name '*.tf' -o -name 'go.mod' -o -name '.terraform.lock.hcl' ) 2>/dev/null 
  | xargs grep -lE 'gocommunity-io/dockerd|kreuzwenker/docker|gocommunity.io/orderedbtree|gogets.dev/btreex' 2>/dev/null

Si alguno de los comandos devuelve un resultado, la máquina en la que se ejecutó ese terraform init o go mod tidy se considera comprometida. No sirve con desinstalar el paquete: el segundo stage se lanza detached y puede seguir ejecutándose incluso después de borrar el provider.

Distinguir el typosquat

El proveedor legítimo kreuzwerker/docker (con r) tiene más de 56 millones de descargas. La copia maliciosa kreuzwenker/docker (con n) tiene menos de 1.500. Si tu required_providers termina en werker es legítimo; si termina en wenker, es el typosquat. Vale revisar carácter por carácter, porque un snippet copiado de un tutorial viejo o de un chat de Slack puede arrastrar la versión mala. El siguiente comando permite automatizar la verificación contra la lista oficial de namespaces:

# Verificar namespaces contra el Registry oficial de HashiCorp
for ns in gocommunity-io/dockerd kreuzwenker/docker; do
  curl -sI "https://registry.terraform.io/v1/providers/${ns}/versions" | head -1 | 
    grep -q '200 OK' && echo "ALERTA: $ns está en el Registry — bloquealo" || echo "$ns no responde"
done
# Verificar que el namespace legítimo sigue intacto
curl -sI 'https://registry.terraform.io/v1/providers/kreuzwerker/docker/versions' | head -1

El proveedor kreuzwerker/docker legítimo responde 200 OK con la lista de versiones; los dos namespaces maliciosos también aparecen en el Registry pero con un conteo de descargas mucho menor (visible en la página de cada provider). El comando de arriba no es exhaustivo, pero sirve como un tripwire rápido: si cualquiera de los dos namespaces devuelve un 200 desde tu CI runner, ya los tienes descargados.

Qué hacer si encuentras una coincidencia

Aikido y Purple Shield coinciden en una respuesta de 72 horas con tres fases:

Horas 0-24: determinar alcance

Antes de tocar nada, hay que saber si el paquete se ejecutó alguna vez. Un grep en tus propios repos no alcanza: contratistas y consultores pueden correr Terraform desde sus laptops contra tu cuenta de AWS. Pedí a cada equipo externo que ejecute los greps de arriba sobre sus propias máquinas y caches de CI, y que reporten cualquier hit antes de continuar.

Horas 24-48: aislar y rotar

Si hubo un hit, esa máquina se considera comprometida. Aíslala de la red y rota toda credencial que haya tocado esa máquina, empezando por credenciales de cloud (AWS access keys, GCP service accounts, Azure service principals), después tokens de publicación de GitHub/GitLab, npm/PyPI/Go, y finalmente claves SSH. Asume que el atacante pudo haber actuado con credenciales válidas durante la ventana de exposición: revisa commits recientes, publicaciones de paquetes y ejecuciones de Terraform de esa máquina en busca de actividad no autorizada. El siguiente bloque resume la rotación de credenciales de AWS como ejemplo replicable a otros proveedores:

# Rotar AWS access keys del usuario IAM comprometido
aws iam create-access-key --user-name operador-comprometido
aws iam delete-access-key --user-name operador-comprometido --access-key-id AKIA-ANTIGUA
# Invalidar sesiones activas (forzar re-autenticación)
aws iam delete-login-profile --user-name operador-comprometido || true
# Revocar tokens de sesión STS que puedan estar vivos
for sid in $(aws iam list-access-keys --user-name operador-comprometido --query 'AccessKeys[].AccessKeyId' --output text); do
  aws iam update-access-key --user-name operador-comprometido --access-key-id "$sid" --status Inactive
done

El patrón se repite para GCP (gcloud iam service-accounts keys create/delete) y Azure (az ad app credential reset). El detalle crítico es que no alcanza con crear nuevas keys: hay que invalidar las viejas y forzar que cualquier sesión abierta en otras máquinas también se cierre. El atacante tiene minutos entre que se publica el incidente y que tu equipo rota, no horas.

Horas 48-72: reimagen y reportar

Como el malware ejecuta un segundo stage detached, borrar el provider no garantiza que todo lo que lanzó esté gone. La única forma de estar seguro es reimaging la máquina desde cero. Si el incidente toca datos regulados (SOC 2, HIPAA, PCI DSS), o si tu póliza de cyber insurance tiene cláusulas de aviso temprano, notificá a tu broker y a tu cliente antes de los 30 días para no complicar un eventual reclamo.

Por qué el lock file no te salva

Terraform ya graba los checksums de los providers en .terraform.lock.hcl, lo que evita que un provider cambie bajo tus pies sin que te des cuenta. Pero ese lock no distingue entre el provider legítimo y el typosquat: si tu required_providers dice kreuzwenker/docker desde el primer commit, el lock fija ese hash y lo respeta. La defensa contra paquetes maliciosos tiene que ocurrir antes, en code review, con alguien que lea el namespace completo y lo confirme contra la página oficial del Terraform Registry.

El problema es que la mayoría de las revisiones se enfocan en la lógica del recurso (aws_instance, docker_container) y no en el namespace del provider. Una regla útil en pipelines que ya tienen review obligatorio: cualquier cambio a un bloque required_providers requiere aprobación explícita de un segundo revisor, y ese revisor verifica carácter por carácter que el namespace exista en registry.terraform.io.

La cobertura que tu tooling probablemente no tiene

Una observación incómoda del reporte: Aikido, la empresa que descubrió el malware, admite que su propio producto todavía no monitorea el Terraform Registry. Cubre los dos módulos Go maliciosos pero no los dos providers Terraform. Si el vendor que encontró esto tiene un gap en Terraform, vale la pena preguntarle al tuyo qué cubre. La mayoría de las herramientas de supply chain security (Snyk, Socket, Dependabot) se enfocan en npm, PyPI, Maven, e imágenes de contenedor; los providers de Terraform forman un árbol de dependencias separado que pocas plataformas inventarian.

Una medida compensatoria barata, incluso sin comprar nada nuevo, es bloquear a nivel de DNS los dominios gocommunity.io y gogets.dev. Los packages ya están subidos al Registry, pero cualquier intento de fetch del C2 desde una máquina tuya va a fallar. Eso no detiene la infección inicial, pero reduce la superficie de comunicación con el operador. Para implementarlo en una flota Linux mixta con systemd-resolved o unbound:

# Bloqueo a nivel de /etc/hosts (rápido, sin infra extra)
echo '127.0.0.1 gocommunity.io api.gocommunity.io' >> /etc/hosts
echo '127.0.0.1 gogets.dev api.gogets.dev' >> /etc/hosts
# Alternativa con unbound (server DNS autoritativo de la red)
cat >> /etc/unbound/unbound.conf <<'EOF'
local-zone "gocommunity.io" static
local-data "gocommunity.io A 127.0.0.1"
local-zone "gogets.dev" static
local-data "gogets.dev A 127.0.0.1"
EOF
systemctl reload unbound

El bloqueo de DNS también corta el tráfico al contrato Ethereum en arbitrum-sepolia-rpc.publicnode.com y otros nodos RPC, pero esa lista es más larga y cambia. Una política más simple: alertar (no bloquear) sobre conexiones HTTPS a *.rpc.publicnode.com, *.infura.io y *.alchemy.com desde máquinas que no tengan razón legítima para hablar con blockchain. Las laptops de DevOps rara vez lo necesitan, los CI runners casi nunca.

El patrón que viene

Que un actor de amenaza norcoreano monte infraestructura dedicada para simular un ecosistema Go (gocommunity.io, gogets.dev, ambos registrados un día antes que las organizaciones de GitHub que los respaldan) sugiere intención de seguir publicando malware en el ecosistema Go. La inversión no se hace para un solo paquete.

El siguiente paso lógico de un actor así es npm y PyPI de nuevo, con un dominio falso equivalente (npmget.dev, npmcommunity.io) y paquetes que imiten utilities conocidos. Si tu equipo tiene un proceso de revisión de dependencias nuevas en CI, ese proceso debería expandirse para incluir verificación visual del namespace contra la página oficial del registro, no solo el hash o la versión. Una guardia mínima en pipeline GitHub Actions se ve así:

# .github/workflows/check-deps.yml — rechaza PRs con paquetes sospechosos
name: check-deps
on: pull_request
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Bloquear namespaces maliciosos conocidos
        run: |
          BLOCKED='gocommunity-io/dockerd|kreuzwenker/docker|gocommunity.io/orderedbtree|gogets.dev/btreex'
          if grep -rE "$BLOCKED" --include='*.tf' --include='*.hcl' --include='go.mod' --include='go.sum' .; then
            echo "Paquete malicioso detectado en el PR"
            exit 1
          fi
      - name: Alertar sobre dominios C2 conocidos en egress
        run: |
          BLOCKED='gocommunity.io|gogets.dev|portfolio-devs.slack.com|portfolio-testers.slack.com'
          if grep -rE "$BLOCKED" . --exclude-dir='.git'; then
            echo "Dominio C2 referenciado — revisar si es legítimo"
            exit 1
          fi

El workflow de arriba es un primer filtro. No sustituye a un SCA (software composition analysis) dedicado, pero cualquier PR que toque un required_providers, un go.mod o un requirements.txt ahora tiene una guardia automática contra los paquetes identificados en este incidente.

Opinión: vale la pena tomar este caso en serio

Sí, incluso si tu homelab tiene un solo servidor. El número de descargas de los paquetes maliciosos (222 + 1.449) parece bajo, pero un ataque dirigido no necesita escala: necesita una sola laptop con terraform apply contra producción. Y la tendencia del reporte es que el actor está probando la cadena de suministro del ecosistema Go con recursos dedicados, lo que suele preceder campañas más amplias en los meses siguientes.

Si tu equipo corre Terraform in-house, las tres acciones inmediatas son: revisar repos y lock files por los cuatro nombres, identificar quién corre Terraform con credenciales de producción, y reducir la vida útil de esas credenciales (rotación automática cada 24-72 horas, no cada 90 días). El costo de las tres es bajo comparado con un incidente donde la respuesta es a las 48 horas y no a los 15 minutos. Si quieres confirmar que ninguno de los hashes IOCs está ya en tu sistema de archivos:

# Verificar los hashes IOCs de Aikido contra archivos locales
HASHES=(
  '5f892a5424e88a21a3eb3d7f82ebf04d8ac31cdb19ada25153be4165df977d0f'  # import-resource.sqlite3
  'ab01686d87565250fc4989faddb877d793667b07ec217a61cbd798f5695d62f5'  # btreex.sql
)
for h in "${HASHES[@]}"; do
  matches=$(find / -type f -size +100c 2>/dev/null | xargs sha256sum 2>/dev/null | grep "$h")
  if [ -n "$matches" ]; then
    echo "ALERTA: hash $h encontrado en:"
    echo "$matches"
  fi
done

El script asume GNU find y sha256sum (presente en cualquier distro Linux estándar). En macOS reemplaza sha256sum por shasum -a 256. La búsqueda es pesada pero completa: cualquier archivo en disco mayor a 100 bytes se hashea y se compara contra los IOCs. Si devuelve un hit, tienes un payload de segunda etapa en algún lado del sistema — reimaging inmediato.

Preguntas frecuentes

¿Por qué Alerta Sysadmins: Malware Go Distribuido Vía Módulos Terraform y Go es relevante para sysadmins Linux?

Forma parte del flujo diario de administración. Los conceptos explicados se aplican directamente a servidores en producción y resuelven problemas reales que enfrentan sysadmins hispanohablantes.

¿Qué necesito para empezar con Alerta Sysadmins: Malware Go Distribuido?

Distribución Linux actualizada (Debian, Ubuntu, RHEL, Fedora o Arch), acceso root, y conexión a internet. Para producción: entorno de staging separado antes de aplicar cambios.

¿Cuáles son los errores más comunes con Alerta Sysadmins: Malware Go Distribuido?

Los tres más frecuentes: (1) saltar la lectura de logs, (2) aplicar cambios directos en producción sin probar, (3) olvidar permisos y ownership. La documentación y un plan de rollback siempre.

¿Cuándo usar Alerta Sysadmins: Malware Go Distribuido y cuándo una alternativa?

Esta solución es ideal para el caso descrito. Alternativas como Docker, Ansible o configuración manual pueden ser mejores según contexto: portabilidad, escala y reproducibilidad son los factores clave.

¿Cómo migro desde una configuración previa a Alerta Sysadmins: Malware Go Distribuido?

Documentar el estado actual, probar la nueva configuración en staging, validar funcionalmente, y aplicar en producción con rollback plan listo. Los posts de SoyLinux incluyen troubleshooting para los problemas más comunes.

Lee también

Alfonso Moreno | Sysadmin Senior

Soy Alfonso, sysadmin senior con RHCE y más de 10 años administrando sistemas Linux para empresas y proyectos personales. Me especializo en infraestructura, redes, virtualización, monitoring, automation y self-hosting. Sobre SoyLinux.com SoyLinux.com es un blog técnico en español sobre Linux, software libre y self-hosting. Lo mantengo en mi tiempo libre. Áreas de expertise - Linux server administration: RHEL, CentOS, Rocky, Alma, Fedora, Debian, Ubuntu - Network services: Postfix, BIND, Samba, NFS, Squid - Virtualización y containers: KVM, Proxmox, Docker, Podman - Monitoring: Prometheus, Grafana, Zabbix - Automation: Ansible, Bash scripting - Self-hosting: Vaultwarden, Nextcloud, Jellyfin, Immich, Homelab Contacto - Email: [email protected]

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *