O provider roda onde estão as chaves
A Aikido encontrou um RAT norte-coreano distribuído como dois providers Terraform no registro da HashiCorp. terraform init baixa um provider, terraform plan o executa como processo local, e o provider lê suas credenciais de nuvem, porque é exatamente para isso que um provider serve. A carga só se decifra para os nomes de variáveis de uma única vítima, de modo que os scanners que baixaram o pacote viram código inerte. Dez das dezoito máquinas alcançadas eram Macs. O Bromure Agentic Coding dá a esse binário uma VM, uma chave secreta falsa e uma lista de lugares que ele pode chamar.
Todo controle que a indústria construiu para agentes de código fica a montante
do comando: aprovar o plano, ler o diff, restringir o push. Nenhum deles fica
entre um binário de provider baixado e o ~/.aws.
Você está adicionando um contêiner a uma stack. Pede ao agente um provider
Docker, ele escreve quatro linhas de HCL, e você roda terraform init. O
Terraform resolve o provider a partir do registro, baixa, confere a assinatura e
guarda em cache. Então você roda terraform plan, e o Terraform inicia esse
binário como um processo na sua máquina e fala com ele por RPC. Ele se autentica
na sua conta de nuvem pelo caminho, usando as mesmas variáveis de ambiente e o
mesmo perfil ~/.aws que você usa.
Cada passo disso é o desenho documentado, e neste mês foi também uma rota de entrega de malware.
O que a Aikido encontrou
Em 22 de setembro a Aikido relatou um cavalo de Troia de acesso remoto escrito em Go, distribuído por meio de dois providers Terraform e dois módulos Go. O The Hacker News cobriu no dia seguinte, chamando de a primeira vez que atacantes usaram o registro que a HashiCorp opera para entregar uma carga. Checkmarx, JFrog e SafeDep somaram suas análises.
Os providers são gocommunity-io/dockerd, com 222 downloads, e
kreuzwenker/docker, com 1.449. O segundo está a uma letra de
kreuzwerker/docker, um provider real com 56 milhões de downloads reportados. Os
módulos Go são gocommunity.io/orderedbtree, publicado em 11 de agosto, e
gogets.dev/btreex, publicado em 8 de setembro.
A Aikido liga a campanha ao Graphalgo, que a ReversingLabs documentou em fevereiro e atribuiu a operadores norte-coreanos. A mesma chave de assinatura tem aparecido em cargas npm desde abril. As pessoas chegam a esses pacotes por uma oferta de emprego: uma falsa empresa Web3 aborda um desenvolvedor no LinkedIn ou no Facebook e envia um exercício de programação. O repositório do exercício é limpo. A dependência que ele puxa é o malware.
Um provider é um programa, e o Terraform lhe entrega as chaves
O npm install roda os scripts de instalação de um pacote e depois sai de cena.
O terraform init busca um programa inteiro, e rodar esse programa é o evento
principal.
Um provider Terraform é um binário executável que o Terraform Core invoca por RPC. O Terraform o executa porque executá-lo é o que um provider é. Você não vai achar um hook de instalação para remover nem um script de ciclo de vida para auditar, porque aqui a execução é o recurso, e não um efeito colateral de desempacotar um tarball.
O binário também roda no único ponto da sua rede com o caminho mais curto até a produção. A Purple Shield descreve a máquina: “Um provider malicioso, portanto, executa na máquina da pessoa que implanta sua infraestrutura, com o acesso que essa pessoa tiver.” E: “um laptop que roda terraform apply normalmente tem chaves de provedor de nuvem, tokens Git, chaves SSH e arquivos de estado do Terraform ao alcance.”
Um ladrão de npm precisa vasculhar seu diretório home atrás de credenciais. O Terraform as entrega ao provider.
Endereçado a uma pessoa
Esses providers atacam um alvo e ignoram todos os demais que os instalam.
Ambos carregam um ponto de entrada oculto em
/internal/provider/resource_docker_container_funcs.go que dispara somente
quando o SHA256 das variáveis Terraform containerName e networkID,
concatenadas, é igual a um valor fixo no código. O código então usa esse hash
como chave AES para decifrar um caminho de arquivo dentro do pacote, descompacta
o que encontra ali, decifra cada arquivo e executa o resultado como pacote Go com
um go run . avulso. Eles esconderam o arquivo no diretório de exemplos, em um
arquivo chamado import-resource.sqlite3.
Ou seja, o atacante precisa saber os nomes que você deu a duas variáveis, e este atacante sabia: o engodo da oferta de emprego foi como ele os aprendeu. Todo pesquisador e todo scanner que baixou o pacote recebeu código que fica ali sem fazer nada. Não dá para detectar o que nunca se decifra.
O segundo estágio é um cavalo de Troia de acesso remoto. Ele primeiro reporta
informações do sistema, incluindo atributos de hardware, SO, hostname e se node
está disponível na máquina, e então espera instruções em dois lugares. Um é o
Slack: um workspace chamado portfolio-devs, um canal chamado frontend-devs,
e depois um segundo workspace com um canal chamado qa-announcements. A Aikido
contou ali 1.240 mensagens cifradas desde 16 de julho. O outro é um smart
contract na testnet Arbitrum Sepolia, em um endereço fixo no código, com 1.402
transações desde 6 de agosto. O malware consulta a blockchain a cada três
segundos e o Slack a cada dez.
Ambos os canais são HTTPS comum para destinos de boa reputação. Suas máquinas de desenvolvimento falam com o Slack o dia inteiro, e uma delas falando com um endpoint RPC público de Ethereum parece normal em qualquer semana em que o time entrega algo on-chain. A mesma turma usou um contrato Sepolia no pacote npm que a Checkmarx descreveu na semana passada, e a reutiliza abertamente porque funciona abertamente.
A Aikido recuperou a lista de vítimas do próprio Slack do atacante: 18 hostnames únicos em 725 mensagens de check-in. Três Windows, cinco Linux, dez macOS.
O que muda em um workspace Bromure
A campanha veio atrás de uma credencial. No Bromure Agentic Coding, o
~/.aws/config da VM não contém sua chave de acesso secreta. Ele aponta para um
helper credential-process que fornece, por um socket virtio, o seu ID de chave de
acesso real emparelhado com um secret falso de 40 caracteres. O manual lista
as ferramentas que adotam isso sem nenhuma configuração: todo SDK da AWS, a CLI
aws, o boto3 e o terraform. O provider lê a cadeia de credenciais como espera,
obtém um perfil bem formado e assina suas requisições com um secret que não vale
nada.
O host conserta essa assinatura na saída. Seu resigner reconhece o tráfego
destinado a *.amazonaws.com, retira a assinatura SigV4 condenada do guest e
assina de novo com o secret real antes de a requisição deixar o seu Mac. Seu
terraform plan funciona. A AWS responde a uma cópia roubada da credencial com
InvalidSignatureException.
Então o secret falso tenta sair. O proxy varre os cabeçalhos e o corpo de cada requisição de saída em busca dos valores de marcação do próprio workspace, usando um autômato Aho-Corasick barato o bastante para rodar em todas elas. Mande um falso para um host para o qual ele não foi cunhado, um webhook do Slack por exemplo, e o proxy responde HTTP 451 e não encaminha um byte. A VM pausa. Uma linha vermelha Credential brokering aparece na Security Timeline, e o Bromure marca o workspace como comprometido, de modo que o seu próximo lançamento oferece apagar o disco e o home dele preservando seus tokens, chaves SSH e configurações.
O C2 ainda precisa sair da máquina. A saída é um conjunto ordenado de regras de
permitir e negar, avaliado de cima para baixo, terminando em default allow ou
default deny. Um workspace que implanta infraestrutura precisa de
registry.terraform.io, releases.hashicorp.com e da sua API de nuvem, o que
deixa portfolio-devs.slack.com e um endpoint RPC do Arbitrum fora da lista. O
host aplica as regras duas vezes: o switch virtual casa pelo IP de destino e pelo
hostname captado por DNS, em TCP e UDP, e o proxy casa de novo pelo nome de
servidor TLS. O switch desvia a porta 80 e a porta 443 para o proxy sem nenhuma
variável de ambiente envolvida e sem nada que o guest possa desligar. Cada
veredito vira uma linha Firewall na Security Timeline.
Você aprova as exclusões
Mais um controle fica entre o provider e sua conta, e é o que importa quando o atacante quer sua infraestrutura em vez dos seus segredos.
Credenciais AWS carregam uma política de escrita, e workspaces novos vêm por
padrão em Prompt before write. O proxy classifica cada chamada pelo nome da
ação no cabeçalho X-Amz-Target ou no parâmetro Action=: Get*, List* e
Describe* são leituras e passam sem uma palavra, enquanto Delete*,
Terminate*, Revoke* e parentes contam como destrutivas. Uma escrita para em
um diálogo intitulado Allow write on "<scope>" from workspace "<name>"?, que
mostra a operação na íntegra em vez de um resumo, com Allow for 15 minutes,
Allow once, Allow for the rest of the session e Don't allow. Recuse,
e o guest recebe uma AccessDeniedException que o agente reporta antes de seguir
em frente.
Quem clica nesse diálogo é você, no seu Mac. Um processo dentro da VM não consegue alcançá-lo, e quem está do outro lado do canal do Slack também não.
Trate a toolchain como um executor
terraform init e terraform plan são baixar-e-executar. Pergunte o que um
provider executa por dentro, porque ele vai executar.
Não deixe segredo vivo ao alcance
Dê ao provider uma cadeia de credenciais que o satisfaça e um secret que não vale nada. O host assina, do outro lado da fronteira, restrito ao destino a que a chave pertence.
Nomeie os destinos primeiro
Um workspace de implantação precisa de uma lista curta e estável: o registro, o host de releases, sua API de nuvem. Escreva-a uma vez, coloque o tráfego sem correspondência em Deny, e um contrato usado como caixa postal não tem o que consultar.
Faça o irreversível perguntar
Deixe as leituras correrem em silêncio. Faça Terminate* lhe custar um
diálogo em uma tela que a VM não alcança.
Ninguém encontrou uma vulnerabilidade aqui
O registro da HashiCorp serviu o pacote que lhe foi pedido. O Terraform conferiu a soma de verificação que recebeu e executou o provider do jeito que executa todo provider. Nenhum sandbox cedeu, nenhum agente leu uma instrução envenenada e nenhum modelo se comportou mal.
Dois providers com 1.671 downloads somados alcançaram ao menos 18 máquinas, a maioria Macs, e ficaram ali consultando um smart contract a cada três segundos. Comportamento documentado carregou a cadeia inteira. A única parte inédita, uma carga que permanece cifrada a menos que suas variáveis tenham um nome específico, existe para que nada consiga encontrá-la.
Decida essas coisas antes da sessão, então, porque você não vai pegá-las durante. Não dá para escanear um provider que não decifrou sua carga, e você não vai separar a exfiltração do tráfego para o Slack. Rode a implantação onde as chaves são iscas, os destinos são uma lista que você escreveu, e a máquina que é comprometida é uma que você joga fora.