TL, DR
Um grupo de cinco pacotes npm, publicado em duas contas diferentes, enviou um postinstall Gancho que lê as credenciais da nuvem do host e as envia para fora do dispositivo. Os pacotes carregam nomes fantasmas e piratas — coral-wraith, ecto-corsair-whisper-6f3b9, ecto-corsair-flag-x9m4, ecto-rust-read-f3a9c1, ecto-nightly-spirit — e serializar tudo o que coletam em uma falsificação ecto_module: Manifesto YAML antes de transmiti-lo. Monitoramos o cluster como Ectoplasma.
A carga útil só é executada quando detecta um ambiente específico: um host cujo nome é um string hexadecimal de 12 caracteres e um diretório de trabalho em /app/node_modules — o formato de uma compilação conteinerizada ou de um worker de CI. Quando esse ponto de controle é aprovado, o gancho consulta o Serviço de metadados de instância da AWS (IMDSv2) Para credenciais de função IAM, enumera Gerenciador de segredos da AWS em três regiões, despeja variáveis de ambiente, lê arquivos em /appe busca sequências de captura de bandeira. Em seguida, exfiltra o resultado de duas maneiras: um beacon para um webhook.site coletor e um PUT de manifesto para um endpoint raw-IP, com uma lista de fallbacks que priorizam o localhost.
As descrições posteriores dos pacotes diziam "carga útil CTF para testes da cadeia de suprimentos do Verdaccio". Relatamos essa autodescrição como um fato observável. O comportamento em si — saída em tempo real para um IP público, leituras reais de credenciais do IMDS, chamadas reais ao Secrets Manager — é o que é, independentemente do rótulo, e é por isso que essas versões foram classificadas como maliciosas.
Um nome no grupo, coral-wraith, não parou em um único lançamento. Foi republicado em rápida sucessão, em dezenas de versões, em questão de horas — 1.0.0 subindo para 6.0.0 — e uma edição anterior com o mesmo nome havia usado inflado 9999.0.x números de versão, o formato clássico de um tentativa de confusão de dependênciaAo longo desse processo, a carga útil amadureceu visivelmente: de um simples sinalizador de enumeração de hosts para uma completa solicitação de credenciais da AWS, envolta em verificações de ambiente que a mantêm discreta fora do alvo pretendido.
| PACOTES | 5 nomes; coral-wraith sozinho republicado em dezenas de versões |
| Ecossistema | npm |
| Instalar vetor | postinstall script de ciclo de vida |
| Alvo primário | Credenciais de função do AWS IAM + valores secretos do Secrets Manager, variáveis de ambiente, /app arquivos |
| Exfilar | webhook.site beacon + raw-IP C2 PUT |
| Porta de disparo | Nome do host em hexadecimal (12 caracteres) + /app/node_modules cwd, além de uma verificação de ambiente que suprime a carga útil fora desse contexto. |
| Gravidade | Alto — divulgação de credenciais em nuvem e segredos gerenciados de ambientes de construção e execução conteinerizados |
Anatomia do ataque
Cada pacote no cluster é construído da mesma maneira: um arquivo quase vazio. index.js (module.exports = {}), uma linha package.json roteiro — “postinstall”: “node postinstall.js” — e a carga útil em postinstall.jsA instalação do pacote é suficiente para executar o gancho; nenhuma importação ou chamada é necessária.
O portão de direcionamento. Antes de fazer qualquer coisa, a carga útil da família ecto verifica o ambiente ao seu redor:
function isAppWorker(): host = os.hostname() if host does NOT match /^[0-9a-f]{12}$/ -> exit if cwd does NOT contain "/app/node_modules" -> exit if cwd contains "/tmp/npm-safe" -> exit otherwise -> proceed Um nome de host de 12 caracteres hexadecimais é o formato padrão que o Docker atribui a um contêiner, e /app/node_modules é um caminho de instalação convencional dentro do contêiner. A terceira cláusula falha se o caminho parecer um diretório de extração em sandbox. O efeito final é que a carga útil permanece inativa em um laptop de desenvolvedor ou em um sandbox de análise e só é ativada dentro de um contêiner de compilação ou de um worker de tempo de execução — o tipo de ambiente com maior probabilidade de conter credenciais de nuvem ativas. O pacote mais antigo no cluster, espectro-coral, não possui esse mecanismo de controle e executa sua coleta (mais simples) incondicionalmente.
ColeçãoQuando o portão passa, o gancho se abre através dele. execFileSync(“/bin/sh”, [“-c”,…]) e executa um único comando composto que, na seguinte ordem:
1. PUT /latest/api/token to 169.254.169.254 (IMDSv2 token request) 2. GET .../iam/security-credentials/ (IAM role name) 3. GET .../iam/security-credentials/<role> (temporary credentials) 4. dump env | sort (environment variables) 5. list /app (excl. node_modules) + cat first 15 (application files) 6. aws secretsmanager list-secrets (us-east-1, eu-west-1, eu-central-1) 7. scrape readable files for HTB{...} (capture-the-flag strings) Os passos 1 a 3 são um procedimento padrão de recuperação do IMDSv2: solicite um token de sessão e, em seguida, anexe-o como o X-aws-ec2-metadata-token O cabeçalho serve para extrair a função IAM da instância e as chaves de acesso temporárias dessa função. A escolha foi implementar o IMDSv2 em vez do IMDSv1, mais simples e sem autenticação. ENTRE Vale ressaltar que isso significa que a carga útil funciona mesmo em instâncias configuradas para exigir acesso a metadados baseado em token, que é a medida de segurança recomendada pela AWS. As credenciais retornadas na etapa 3 têm curta duração. ID da chave de acesso/Chave de Acesso Secreta/Token Triplas com escopo definido para a função da instância; tudo o que essa função pode fazer, o detentor dessas chaves pode fazer durante a vigência da credencial.
Os passos 4 a 6 ampliam a tomada. env O dump captura tudo o que o processo de compilação ou de tempo de execução herdou — na prática, é aqui que geralmente ficam os tokens de registro, as strings de conexão do banco de dados e as chaves de API. / aplicativo O comando `file walk` lê até quinze arquivos de aplicativos externamente. node_modules, que pode revelar a configuração, .env arquivos ou fonte. A etapa 6 chama aws secretsmanager listar-segredos em três regiões; as credenciais obtidas nas etapas 1 a 3 são exatamente o que autentica essas chamadas, portanto, a leitura do IMDS e a enumeração do Secrets Manager se unem em uma única escalação: função da instância → inventário de segredos gerenciados. A etapa 7 faz alusão à abordagem de captura de bandeira — quando um HTB{…} Se a sinalização for encontrada, ela é enviada separadamente; caso contrário, o objeto bruto coletado é dividido em quatro partes e enviado.
As versões posteriores do cluster levam a escalação adiante. Em vez de parar em um inventário, elas analisam a resposta do IMDS e exportam as chaves temporárias como AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_SESSION_TOKEN variáveis de ambiente, confirme a identidade com aws sts obter-identidade-do-chamadore então percorra cada segredo retornado por lista-segredos chamada aws secretsmanager obter-valor-secreto em cada um — recuperando o conteúdo secreto, não apenas seus nomes. As mesmas versões também leem binários de flags substituídos por processo (/lerflag e amigos) e tentar um corrida de carga contra qualquer projeto Rust encontrado em / aplicativo, ampliando a coleta para além das credenciais da nuvem, abrangendo tudo o que o ambiente de compilação expõe.
Essas versões posteriores também se restringem de forma mais agressiva. Além do nome de host hexadecimal de 12 caracteres e /app/node_modules Durante as verificações, o payload inspeciona a configuração ativa do registro de pacotes e o caminho do diretório de trabalho, encerrando-se silenciosamente quando estes indicam um contexto de análise ou espelhamento em vez de um alvo em execução. O efeito combinado é um payload que não realiza nenhuma ação observável na maioria dos ambientes de inspeção e executa sua coleta completa apenas onde considera estar em um host conteinerizado genuíno.
exfiltrationOs dados coletados saem do host por dois canais. Primeiro, um beacon. POST para um fixo webhook.site coletor, contendo o nome do host, UID numérico, diretório de trabalho e até 120 KB de dados coletados. Em seguida, os dados são incorporados a um falso "manifesto de módulo" em YAML e PUT para /api/módulos/ em um servidor de destino:
ecto_module: name: "<flag-or-chunk-0>" version: "1.0.0" power_level: "<chunk-1>" ship_deck: "<chunk-2>" cargo_hold: "<chunk-3>" Os nomes dos campos do manifesto (nível_de_potência, convés do navio, porão de carga) são decoração — os dados roubados estão contidos nos valores das strings, razão pela qual um monitor de rede vê o que parece ser um upload benigno de manifesto de registro de pacotes em vez de um despejo de dados óbvio. O canal beacon carrega mais: o POST corpo para webhook.site Inclui o nome do host, o UID numérico, o diretório de trabalho e até 120 KB do blob coletado, de modo que mesmo um único beacon bem-sucedido fornece a informação completa. webhook.site É um serviço gratuito de inspeção de requisições; usá-lo como coletor significa que a operadora nunca precisa configurar sua própria infraestrutura de recepção para esse canal, e as requisições registradas permanecem no repositório do serviço.
O manifesto PUT percorre uma lista de contingência que começa com vários 127.0.0.1/localhost portos e depois cai em três endereços públicos no `154.57.164.0/24` O intervalo, parando no primeiro ponto de extremidade que responde com um status 2xx. A ordem localhost-first é consistente com a autodescrição do "teste verdaccio" (um registro local em loopback), mas os fallbacks para IP público significam que os dados saem do host sempre que o loopback não estiver escutando — ou seja, em qualquer máquina que não seja o próprio ambiente de teste do autor.
Timeline
O cluster demonstra um crescimento incremental de capacidade, em vez de uma queda abrupta. A ordenação é feita com base no comportamento observado, e não no tempo real de publicação:
| Etapa | Pacotes / Versões | Comportamento |
|---|---|---|
| Corrida inicial | coral-wraith 9999.0.x | Números de versão inflados, consistentes com uma tentativa de confusão de dependências; enumeração e exfilagem em tempo de instalação. |
| Seed | coral-wraith 1.0.0 | O postinstall coleta arquivos id/env/flag; um único PUT para 154[.]57[.]164[.]71:30782, marcador ECT-472839 |
| Iteração rápida | coral-wraith 1.0.1 → 6.0.0 | Dezenas de lançamentos em poucas horas; a carga útil ganha isAppWorker() portão, extração de credenciais IMDSv2, o completo get-secret-value pivô, verificação do ambiente de registro/caminho e marcadores de destino duplo |
| Nomes paralelos | ecto-corsair-whisper-6f3b9 1.0.14–1.0.18 | Mesma carga útil controlada; webhook.site lista de fallback de beacon e multi-endpoint |
| Variantes | ecto-rust-read-f3a9c1 1.0.1–1.0.2 | Adiciona marcadores de pia extras ECT-987654, ECT-654321, ECT-839201 |
| Variantes | ecto-corsair-flag-x9m4 1.0.0, ecto-nightly-spirit 1.1.0 | Mesma carga útil protegida, mesmo comando e controle (C2) e mesmo sinalizador. |
A característica definidora do cluster é sua cadência de publicação: em vez de um pacote e uma versão, o mesmo nome é republicado repetidamente em rápida sucessão, cada versão sendo uma pequena variação da anterior, juntamente com alguns pacotes irmãos com nomes diferentes, mas que carregam a mesma carga útil. Dentro do sussurro A família de códigos se dividiu em duas impressões digitais semelhantes — um conjunto acionando duas detecções críticas, outro três (um coletor adicional de leitura de arquivo) — mas ambos levam à mesma carga útil; a diferença reside na deriva do código, não em uma bifurcação comportamental. Versões de sussurro Valores além da faixa analisada (até pelo menos 1.0.25 no momento da redação deste documento) foram observados ao vivo no registro, e o espectro-coral O nome continuou a subir na hierarquia de versões ao longo da mesma janela.
Indicadores de compromisso
Todos os indicadores abaixo foram extraídos do código-fonte do pacote no disco. Os indicadores de rede estão desativados.
Network
| Indicador | Tipo |
|---|---|
hxxp://154[.]57[.]164[.]71:30782 | Alvo C2 PUT (coral-wraith) |
hxxp://154[.]57[.]164[.]80:30543 | C2 PUT fallback (ecto-*) |
hxxp://154[.]57[.]164[.]82:31250 | C2 PUT fallback (ecto-*) |
hxxp://154[.]57[.]164[.]71:31289 | C2 PUT fallback (ecto-*) |
hxxps://webhook[.]site/602a4c72-7033-4e28-92ea-dc66e59206e5 | Colecionador de farol |
169[.]254[.]169[.]254/latest/... | Leitura de credenciais IMDSv2 (lado do destino, metadados da AWS) |
Comportamental/arquivo
| Indicador | Tipo |
|---|---|
"postinstall": "node postinstall.js" | Instalar vetor |
ecto_module: YAML com power_level / ship_deck / cargo_hold chaves | esquema de manifesto Exfil |
Marcadores de pia ECT-472839, ECT-987654, ECT-654321, ECT-839201 | Segmento de caminho C2 /api/modules/<marker> |
isAppWorker() portão: anfitrião /^[0-9a-f]{12}$/, cwd contém /app/node_modules | Condição de ativação |
aws secretsmanager list-secrets Acima de us-east-1, eu-west-1, eu-central-1 | Enumeração de segredos |
HTB{...} raspagem de regex | colheita de captura de bandeira |
Hashes de arquivo (sha256, capturados no momento da análise)
| Envie o | sha256 |
|---|---|
coral-wraith/postinstall.js | ce5ff035cfdfed1d0015446424b352c27b66bcb77e9fdb0a51e4245199146824 |
ecto-corsair-whisper-6f3b9 1.0.18/postinstall.js | b58432acba376aa6976f0490d9a1c04257ccdbc856d8390260c50322d63e31c3 |
Atribuição e comportamento observado
Os cinco pacotes foram publicados sob dois nomes de conta npm diferentes, mas compartilham infraestrutura suficiente para serem tratados como um único cluster: o mesmo. ecto_módulo esquema manifesto, o mesmo ECT-472839 marcador de pia primária, o mesmo webhook.site ID do coletor e endpoints C2 no mesmo Bloco 154.57.164.0/24. O pacote de sementes (`coral-wraith`), mais simples e sem restrições) e a família ecto com restrições, portanto, são interpretadas como iterações de um mesmo conjunto de ferramentas, em vez de esforços independentes.
Os pacotes se autodescrevem, em versões posteriores, como “Carga útil CTF para testes da cadeia de suprimentos Verdaccio.” Apresentamos esse rótulo como um fato observável e não o reiteramos como uma descoberta sobre a finalidade. O que o código faz é inequívoco e independente de como é rotulado: ele lê as credenciais de função do IAM do serviço de metadados da instância, enumera os segredos gerenciados em três regiões da AWS e transmite os resultados para um IP público e um coletor de webhook de terceiros. Um ambiente de teste genuinamente voltado para loopback não precisaria da lista de fallback de IP público, das leituras de credenciais do IMDS ou das chamadas do Secrets Manager entre regiões. Como a saída e o alcance das credenciais são reais, as versões com acesso restrito foram classificadas como maliciosas.
O fato de ser exclusivo para contêineres é a característica operacional mais notável. Ele funciona tanto como uma medida de evasão — permanecendo silencioso em laptops e em ambientes de análise isolados — quanto como uma medida de direcionamento, sendo acionado apenas onde é mais provável a presença de uma função IAM real e segredos ativos. Analistas que executam esses pacotes em um ambiente de análise isolado genérico não observariam nada; o comportamento só se manifesta sob um nome de host no estilo Docker e um caminho de instalação dentro do contêiner.
Impacto, tendências e orientações para defensores
A vulnerabilidade aqui reside na divulgação de credenciais e segredos na nuvem dentro dos contêineres de compilação e execução. Uma credencial de função do IAM extraída do IMDS contém todas as permissões que essa função possui; gerenciador de segredos:ListarSegredos (e qualquer continuação) ObterValorSecreto) amplia isso para segredos de aplicativos armazenados. Os despejos de variáveis de ambiente frequentemente contêm tokens de registro, URLs de banco de dados e chaves de API. Em um contexto de CI ou contêiner — exatamente o que o gate seleciona — uma única instalação transitiva de um desses pacotes é suficiente para vazar esse material.
O Ectoplasm se encaixa em um padrão que continuamos a observar: payloads instalados que acessam metadados na nuvem e segredos gerenciados em vez de arquivos locais, e que se restringem a serem executados apenas em ambientes de alto valor. Duas observações defensivas seguem abaixo.
- A forma é detectávelUm gancho de instalação npm/PyPI cujo gráfico de chamadas alcança uma API de segredos na nuvem (aws secretsmanager, segredos do gcloud, cofre de chaves az) ou o endereço IMDS e um coletor de saída de rede é um padrão estreito e de alto sinal — quase nunca ocorre em um script de ciclo de vida legítimo. Análise de fluxo estático É possível sinalizar sem depender de nenhum domínio ou IP específico.
- O endurecimento do ambiente o atenua. Impor o IMDSv2 com um limite de um salto impede que as cargas de trabalho do contêiner acessem os metadados da instância; definir o escopo das funções do IAM para o menor privilégio limita o impacto de qualquer credencial que vaze; e executar instalações com –Ignore-scripts Na integração contínua (CI), o vetor de gancho de instalação é completamente removido para pacotes que não precisam dele.
Para os responsáveis pela segurança, as verificações práticas são: alertar sobre conexões de saída de contêineres de compilação/CI para IPs públicos não permitidos durante npm installFique atento ao acesso ao IMDS originado de scripts do ciclo de vida do pacote; e considere suspeito qualquer gancho de instalação que execute comandos em uma CLI na nuvem até que se prove o contrário.
Duas observações adicionais específicas para este cluster. Primeiro, como a ativação é restrita a ambientes conteinerizados, um pacote que parece inerte quando verificado em uma estação de trabalho ainda pode estar ativo em produção — a verificação precisa reproduzir as condições de nome do host e caminho do contêiner, ou ler o código-fonte diretamente, em vez de confiar em "Eu instalei e nada aconteceu". Segundo, o uso de um serviço público de inspeção de requisições como coletor de beacons significa que alguns dos dados exfiltrados podem ser recuperados para resposta a incidentes: uma organização que encontra um desses pacotes em sua árvore de dependências pode deduzir o que um beacon bem-sucedido teria contido a partir da lógica de coleta do payload e deve rotacionar quaisquer credenciais de função do IAM, tokens de registro e segredos gerenciados que eram acessíveis a partir do ambiente de compilação ou execução afetado. Rotação de credenciaisA solução eficaz após a instalação, e não a remoção do pacote, é o processo de correção.





