· Allan Lancioni

Como identifiquei um golpe em “teste técnico”: análise real de um malware direcionado a desenvolvedores

O ataque não dependia de vulnerabilidade técnica, dependia de confiança.

Por que esse caso é importante#

Essa semana recebi uma oportunidade “imperdível” no LinkedIn: vaga sênior, salário em USD muito acima da média, remoto, flexível.

Troquei algumas palavras com suposto CEO da empresa, e então ele mandou o teste técnico.

Era só um projeto React.

Mas se eu tivesse rodado um npm start sem olhar o código, teria entregado chaves, dados pessoais, histórico do navegador e até dinheiro.

Nesse artigo faço a engenharia reversa de um golpe que recebi, explicando como funciona o ataque pelos vetores técnico e social, e mostrando como se prevenir.

1. Identificando o primeiro contato#

O golpe começa no LinkedIn.

O alvo costuma ser desenvolvedores experientes, especialmente quem já trabalhou com blockchain, criptomoedas ou devs JavaScript. São perfis de alto ROI para o atacante.

O “recrutador” se apresenta como CEO de um projeto novo. O perfil parece legítimo, a empresa também: site básico, descrição coerente, JD bem escrita e vaga pagando acima da média.

O fluxo é:

  • ele pede seu CV e GitHub
  • diz que você “passou na primeira etapa”
  • afirma que a próxima etapa é uma entrevista técnica
  • mas antes disso, te envia a codebase para “estudo”, supostamente para você se preparar para a entrevista

É aí que o ataque acontece.

Mensagem do suposto CEO com um link de repositório do GitHub, borrado, pedindo que eu examine a UI, a estrutura e o fluxo do projeto antes da conversa
Mensagem do suposto CEO

Red flags: salário alto demais, buzzwords como blockchain/cripto, codebase antes de entrevista, urgência no processo, perfis e sites com sinais de simulação

2. O primeiro alerta: um script de start “inocente” demais#

Ao abrir o repositório, algo no package.json me chamou atenção:

"start": "npm run check-env && npm run app:pre | react-scripts start

Antes de iniciar o projeto React, dois scripts eram executados: check-env e app:pre.

Ao abrir o check-env, encontrei algo bem específico: o script bloqueava a execução em VSCode, GitPod e outros ambientes isolados.

Por que um projeto React impediria execução em ambiente isolado?

Isso é uma forma do código garantir execução no terminal real, com acesso ao Sistema Operacional.

Trecho de check-environment.js: um if que detecta Gitpod ou Codespaces, imprime que rodar o projeto naquele terminal não é suportado, e encerra o processo
Código bloqueando execução em ambientes isolados

3. Usando o Copilot como scanner de anomalias#

Não rodei o projeto.

Pedi ao Copilot para scanear o projeto em busca de potenciais ameaças.

Ele encontrou isto:

Relatório do Copilot apontando Remote Code Execution como ameaça principal em frontController.js: uma URL em Base64, o envio de todas as variáveis de ambiente para um servidor externo, e a execução do que volta via new Function
Scanner do Copilot indicando padrões suspeitos

O arquivo frontController.js, executado em npm run app:pre, em ~15 linhas, tinha:

  • um token grande que na verdade era uma URL ofuscada em Base64
  • um uso perigoso de new Function
  • uma chamada a verify() que enviava dados para um servidor desconhecido

Comecei a investigar.

4. O ponto crítico: Remote Code Execution (RCE)#

Havia apenas um trecho de código suspeito no projeto, e ele estava disfarçado como "verificação de API".

Vamos analisar o código de ataque:

Bloco de código com uma constante VERIFICATION_TOKEN em Base64 e a função validateApiKey, que chama verify e executa a resposta do servidor com new Function, tudo sob mensagens de log que falam em verificar a chave de API
Código malicioso

Esse bloco é o coração do ataque. As funções auxiliares ajudam a mascarar o fluxo:

As duas funções auxiliares: setApiKey, que é apenas um atob, e verify, que faz um axios.post enviando todo o process.env com um header de segredo
Funções auxiliares do código malicioso

O que realmente acontece aqui#

  • ao iniciar o projeto, a função validateApiKey() é executada
  • o "Token" é uma URL escondida em Base64
  • setApiKey() decodifica essa URL
  • verify() envia uma requisição HTTP para esse servidor externo
  • o servidor responde com um script JavaScript completo
  • o projeto executa esse script localmente via new Function("require", response.data)

O new Function funciona como uma porta aberta no ambiente de execução: ele aceita uma string e transforma aquilo em código real. É o mesmo princípio do eval, só com outra sintaxe.

Se você rodar new Function vindo da internet, não é azar quando dá errado. É consequência.

Isso é Remote Code Execution (RCE). O atacante controla o que a sua máquina executa no momento em que você roda o projeto.

O repositório é só o “veículo”. O ataque real está no servidor.

5. Testando o endpoint em ambiente controlado#

Ao decodificar o Base64, encontrei a URL:

https://ip-api-test.vercel.app/api/ip-check-encrypted/3aeb34a31

Via GET, devolvia apenas o IP. Normal.

Mas via POST, replicando headers e variáveis do projeto, recebi exatamente o esperado:

Resposta do servidor em um terminal: cabeçalhos de um app Express seguidos de um corpo de centenas de milhares de caracteres, tudo em hexadecimal e nomes de variáveis ofuscados
Trecho do payload malicioso retornado pelo servidor via CURL

Um payload de ~700k caracteres, totalmente ofuscado, ou seja, modificado para dificultar a leitura.

O mesmo payload passado por um beautify: dá para ler hostname, plataforma, diretório home, um hostURL montado por concatenação de strings, e funções de caminho de arquivo ainda ofuscadas
Código do malware ofuscado parcialmente reconstruído com beautify.js

6. O que o script fazia de verdade#

Era malware. Um stealer voltado para cripto.

Reconstruindo trecho a trecho com LLMs, ficou claro o funcionamento:

  • famílias de browsers separadas
  • caminhos específicos por navegador
  • lista de IDs de wallets (MetaMask, Binance, etc...)
  • arquivos-alvo por wallet
  • domínios de destino para envio
Código reconstruído com os caminhos de perfil de Brave e Chrome nos três sistemas operacionais, e uma função uploadFiles com a lista de IDs de extensão da Chrome Web Store para MetaMask, Binance, Phantom, TronLink, Ronin, Keplr e Solflare
Código reconstruído mostrando browsers e wallets

O alvo: roubar wallets e sessões armazenadas no navegador do desenvolvedor.

7. Conclusão#

O que mais chamou atenção nesse caso não foi o código malicioso em si, mas o contexto em que ele foi entregue.

O ataque não dependia de vulnerabilidade técnica, dependia de confiança.

Uma oferta boa demais, um “teste técnico”, um projeto aparentemente legítimo.

Uma única ação esperada: rodar o código.

Reflexos que vale desenvolver#

  • Leia o que vai rodar. Especialmente start, prestart, postinstall.
  • Código desconhecido só em ambiente isolado. Use sandbox: WSL, VM ou Docker.
  • IA como scanner. Um Copilot ou LLM lendo o código com você pode ser bem útil.
  • Repo falso deixa cheiro. Commits nonsense, autores novos, nada sendo realmente modificado...
Lista de commits de um repositório: quatro mensagens genéricas do tipo "Implement feature in topIcon4.png" e "Add tests in readme.txt", de autores diferentes, todas no mesmo dia, três delas sem verificação de assinatura
Sinais de simulação em repositório aparentemente legítimo

Ler em markdown