← Voltar para todas as publicações
Publicado em · por Renaud Deraison

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.

O caminho documentado, e o ramo que lhe acrescentaramo registrokreuzwenker/dockerinita máquina que implantaTerraform Coreplan → inicia, e RPCo processo do providerum binário baixadoas credenciais~/.aws, tokens, chaveso ramo que ninguém pediu, dentro do providersha256(containerName + networkID)bate → chave AES → decifra → go run . avulsoqualquer outra configuração, e todo scannero hash não bate; a carga nunca é decifradaa única configuração a que foi endereçadaum RAT, que reporta hostname e hardware, e espera18 máquinas, 725 mensagens: 3 Windows, 5 Linux, 10 macOS
A cadeia não tem exploit nenhum. terraform init resolve um provider a partir do registro e guarda o binário em cache; terraform plan o inicia como processo local e fala com ele por RPC; o provider lê a cadeia de credenciais ambiente, que é o protocolo. Os providers do Graphalgo acrescentam um ramo: um ponto de entrada oculto que aplica hash a duas das suas próprias variáveis Terraform, e só quando o hash bate com um valor fixo no código ele usa esse hash como chave AES para decifrar uma carga escondida em um fixture de exemplo e executá-la com um 'go run' avulso. Para qualquer outra configuração o código é inerte.

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.

Na máquina que implantao processo do provider, rodando como vocêlê ~/.aws → chave secreta reallê tokens git, chaves SSH, tfstateseu diretório home é o raio da explosãosaída, sem questionamentoportfolio-devs.slack.com · a cada 10sArbitrum Sepolia RPC · a cada 3sambos os destinos têm boa reputaçãonão há anomalia para alertarrecuperação: girar tudo que o laptop alcançavaEm um workspace Bromure Agentic Codingo processo do provider, no guestlê ~/.aws → access key ID + secret falsoUbuntu 24.04, sob o hipervisorum disco que se troca por um item de menusaída, confrontada com suas regrasslack.com → sem regra → deny padrãoSepolia RPC → sem regra → deny padrãoe se o falso for levado para fora do escopoHTTP 451, nenhum byte enviado, VM pausadarecuperação: apagar e lançar, sem girar nada
O mesmo binário de provider, duas máquinas. Em um laptop ele roda como você, lê uma chave secreta real da cadeia de credenciais e alcança o Slack e um endpoint RPC público porque ninguém escreveu uma regra contra isso. Em um workspace Bromure ele roda dentro de um guest Ubuntu sob o hipervisor, encontra um ID de chave de acesso emparelhado com um secret falso e encontra um conjunto ordenado de regras de saída no caminho; levar o falso para onde ele não foi cunhado devolve HTTP 451 e pausa a VM. As chamadas AWS que de fato pertencem ao caminho são assinadas de novo no host, então o terraform continua funcionando, e ações destrutivas na AWS param em um diálogo que aparece no Mac.

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.