El provider se ejecuta donde están las claves
Aikido encontró un RAT norcoreano distribuido como dos providers de Terraform en el registro de HashiCorp. terraform init descarga un provider, terraform plan lo ejecuta como proceso local, y el provider lee tus credenciales de nube, porque para eso sirve precisamente un provider. La carga solo se descifra para los nombres de variables de una única víctima, así que los escáneres que descargaron el paquete vieron código inerte. Diez de las dieciocho máquinas alcanzadas eran Macs. Bromure Agentic Coding le da a ese binario una VM, una clave secreta falsa y una lista de sitios a los que puede llamar.
Todos los controles que la industria ha construido para los agentes de código
quedan por delante del comando: aprobar el plan, leer el diff, restringir el
push. Ninguno queda entre un binario de provider descargado y ~/.aws.
Estás añadiendo un contenedor a una stack. Le pides al agente un provider de
Docker, escribe cuatro líneas de HCL, y ejecutas terraform init. Terraform
resuelve el provider desde el registro, lo descarga, comprueba su firma y lo
guarda en caché. Luego ejecutas terraform plan, y Terraform arranca ese binario
como un proceso en tu máquina y le habla por RPC. Por el camino se autentica en
tu cuenta de nube, con las mismas variables de entorno y el mismo perfil
~/.aws que usas tú.
Cada paso de eso es el diseño documentado, y este mes fue además una ruta de entrega de malware.
Lo que encontró Aikido
El 22 de septiembre Aikido informó de un troyano de acceso remoto escrito en Go, distribuido a través de dos providers de Terraform y dos módulos de Go. The Hacker News lo cubrió al día siguiente, calificándolo como la primera vez que unos atacantes usan el registro que opera HashiCorp para entregar una carga. Checkmarx, JFrog y SafeDep añadieron su análisis.
Los providers son gocommunity-io/dockerd, con 222 descargas, y
kreuzwenker/docker, con 1.449. El segundo está a una letra de
kreuzwerker/docker, un provider real con 56 millones de descargas declaradas.
Los módulos de Go son gocommunity.io/orderedbtree, publicado el 11 de agosto, y
gogets.dev/btreex, publicado el 8 de septiembre.
Aikido vincula la campaña con Graphalgo, que ReversingLabs documentó en febrero y atribuyó a operadores norcoreanos. La misma clave de firma aparece en cargas de npm desde abril. A estos paquetes se llega por una oferta de trabajo: una falsa empresa Web3 aborda a alguien que desarrolla en LinkedIn o Facebook y le manda un ejercicio de programación. El repositorio del ejercicio está limpio. La dependencia que arrastra es el malware.
Un provider es un programa, y Terraform le da las claves
npm install ejecuta los scripts de instalación de un paquete y luego se aparta.
terraform init trae un programa entero, y ejecutar ese programa es el acto
principal.
Un provider de Terraform es un binario ejecutable que Terraform Core invoca por RPC. Terraform lo ejecuta porque ejecutarlo es lo que un provider es. No vas a encontrar un hook de instalación que quitar ni un script de ciclo de vida que auditar, porque aquí la ejecución es la funcionalidad y no un efecto colateral de desempaquetar un tarball.
El binario también se ejecuta en el único punto de tu red con el camino más corto hacia producción. Purple Shield describe la máquina: “Un provider malicioso se ejecuta, por tanto, en la máquina de la persona que despliega tu infraestructura, con el acceso que tenga esa persona.” Y: “un portátil que ejecuta terraform apply suele tener a mano claves de proveedor de nube, tokens de Git, claves SSH y ficheros de estado de Terraform.”
Un ladrón de npm tiene que rebuscar credenciales por tu directorio personal. Terraform se las entrega al provider.
Dirigido a una sola persona
Estos providers atacan a un objetivo e ignoran a todos los demás que los instalan.
Ambos llevan un punto de entrada oculto en
/internal/provider/resource_docker_container_funcs.go que solo se dispara
cuando el SHA256 de las variables de Terraform containerName y networkID,
concatenadas, es igual a un valor fijado en el código. El código usa entonces ese
hash como clave AES para descifrar una ruta de fichero dentro del paquete,
descomprime lo que encuentra allí, descifra cada fichero y ejecuta el resultado
como paquete de Go con un go run . desacoplado. Escondieron el archivo en el
directorio de ejemplos, en un fichero llamado import-resource.sqlite3.
Así que el atacante tiene que conocer los nombres que le diste a dos variables, y este atacante los conocía: el señuelo de la oferta de trabajo es como los aprendió. Todos los investigadores y todos los escáneres que descargaron el paquete obtuvieron código que se queda ahí sin hacer nada. No se puede detectar lo que nunca se descifra.
La segunda etapa es un troyano de acceso remoto. Primero informa de datos del
sistema, incluidos atributos de hardware, sistema operativo, hostname y si node
está disponible en la máquina, y luego espera instrucciones en dos sitios. Uno es
Slack: un workspace llamado portfolio-devs, un canal llamado frontend-devs,
y más tarde un segundo workspace con un canal llamado qa-announcements. Aikido
contó allí 1.240 mensajes cifrados que se remontan al 16 de julio. El otro es un
smart contract en la testnet de Arbitrum Sepolia, en una dirección fijada en el
código, con 1.402 transacciones que se remontan al 6 de agosto. El malware
consulta la blockchain cada tres segundos y Slack cada diez.
Ambos canales son HTTPS corriente hacia destinos con buena reputación. Tus máquinas de desarrollo hablan con Slack todo el día, y que una de ellas hable con un endpoint RPC público de Ethereum se lee como normal en cualquier semana en la que el equipo entregue algo on-chain. La misma gente usó un contrato de Sepolia en el paquete npm que Checkmarx describió la semana pasada, y lo reutiliza a la vista porque a la vista funciona.
Aikido recuperó la lista de víctimas del propio Slack del atacante: 18 hostnames únicos en 725 mensajes de check-in. Tres Windows, cinco Linux, diez macOS.
Qué cambia en un workspace de Bromure
La campaña venía a por una credencial. En Bromure Agentic Coding, el
~/.aws/config de la VM no contiene tu clave de acceso secreta. Apunta a un
helper credential-process que entrega, por un socket virtio, tu identificador de
clave de acceso real emparejado con un secreto falso de 40 caracteres. El
manual enumera las herramientas que lo adoptan sin configuración alguna: todos
los SDK de AWS, la CLI aws, boto3 y terraform. El provider lee la cadena de
credenciales como espera, obtiene un perfil bien formado y firma sus peticiones
con un secreto que no vale nada.
El host repara esa firma a la salida. Su resigner reconoce el tráfico destinado a
*.amazonaws.com, despoja al guest de su condenada firma SigV4 y vuelve a firmar
con el secreto real antes de que la petición salga de tu Mac. Tu terraform plan
funciona. AWS responde a una copia robada de la credencial con
InvalidSignatureException.
Luego el falso intenta salir. El proxy revisa las cabeceras y el cuerpo de cada petición saliente en busca de los valores marcadores del propio workspace, con un autómata de Aho-Corasick lo bastante barato como para correr sobre todas. Manda un falso a un host para el que no fue acuñado, un webhook de Slack por ejemplo, y el proxy responde HTTP 451 y no reenvía ni un byte. La VM se pausa. Una fila roja de Credential brokering aterriza en la Security Timeline, y Bromure marca el workspace como comprometido, de modo que tu siguiente lanzamiento ofrece borrar su disco y su home conservando tus tokens, claves SSH y ajustes.
El C2 todavía tiene que salir de la máquina. La salida es un conjunto ordenado de
reglas de permitir y denegar, evaluado de arriba abajo, que termina en
default allow o default deny. Un workspace que despliega infraestructura
necesita registry.terraform.io, releases.hashicorp.com y tu API de nube, lo
que deja portfolio-devs.slack.com y un endpoint RPC de Arbitrum fuera de la
lista. El host aplica las reglas dos veces: el switch virtual coteja la IP de
destino y el hostname captado por DNS, en TCP y en UDP, y el proxy vuelve a
cotejar el nombre de servidor TLS. El switch desvía el puerto 80 y el puerto 443
hacia el proxy sin ninguna variable de entorno de por medio y sin nada que el
guest pueda desactivar. Cada veredicto se convierte en una fila de Firewall en la
Security Timeline.
Tú apruebas los borrados
Un control más se interpone entre el provider y tu cuenta, y es el que cuenta cuando el atacante quiere tu infraestructura en vez de tus secretos.
Las credenciales de AWS llevan una política de escritura, y los workspaces nuevos
vienen por defecto en Prompt before write. El proxy clasifica cada llamada
por el nombre de acción de la cabecera X-Amz-Target o del parámetro Action=:
Get*, List* y Describe* son lecturas y pasan sin una palabra, mientras que
Delete*, Terminate*, Revoke* y sus parientes cuentan como destructivas. Una
escritura se detiene en un diálogo titulado
Allow write on "<scope>" from workspace "<name>"?, que muestra la operación
literal en lugar de un resumen, con Allow for 15 minutes, Allow once,
Allow for the rest of the session y Don't allow. Rechaza, y el guest
recibe una AccessDeniedException que el agente reporta antes de seguir
adelante.
Eres tú quien hace clic en ese diálogo, en tu Mac. Un proceso dentro de la VM no puede alcanzarlo, y quien esté al otro lado del canal de Slack tampoco.
Trata la cadena de herramientas como un ejecutor
terraform init y terraform plan son descargar-y-ejecutar. Pregunta qué
ejecuta un provider por dentro, porque va a ejecutar.
No dejes ningún secreto vivo al alcance
Dale al provider una cadena de credenciales que le satisfaga y un secreto que no vale nada. El host firma, al otro lado de la frontera, acotado al destino al que pertenece la clave.
Nombra primero los destinos
Un workspace de despliegue necesita una lista corta y estable: el registro, el host de releases, tu API de nube. Escríbela una vez, pon el tráfico sin coincidencia en Deny, y un contrato usado de buzón muerto no tiene nada que consultar.
Haz que lo irreversible pregunte
Deja que las lecturas corran en silencio. Haz que Terminate* te cueste un
diálogo en una pantalla que la VM no puede alcanzar.
Aquí nadie encontró una vulnerabilidad
El registro de HashiCorp sirvió el paquete que le pidieron. Terraform verificó la suma de comprobación que le dieron y ejecutó el provider igual que ejecuta todos los providers. Ninguna sandbox se rompió, ningún agente leyó una instrucción envenenada y ningún modelo se portó mal.
Dos providers con 1.671 descargas entre ambos alcanzaron al menos 18 máquinas, la mayoría Macs, y se quedaron ahí consultando un smart contract cada tres segundos. Un comportamiento documentado sostuvo la cadena entera. La única parte novedosa, una carga que sigue cifrada a menos que tus variables se llamen de una forma concreta, existe para que nada pueda encontrarla.
Decide estas cosas antes de la sesión, entonces, porque no las vas a pillar durante. No se puede escanear un provider que no ha descifrado su carga, y no vas a distinguir la exfiltración dentro del tráfico hacia Slack. Ejecuta el despliegue donde las claves sean señuelos, los destinos sean una lista que escribiste tú, y la máquina que acaben rooteando sea una que tiras.