Cadeado escuro preso a uma porta — ilustração de segurança para case anonimizado
Case· Portal de notícias (case anonimizado)

O backdoor que buscava ordens na blockchain: recuperação de um portal WordPress invadido

Um portal de notícias com nove anos de acervo chegou com o site "desconfigurado". Não era desconfiguração: era invasão ativa, com um backdoor que se escondia do administrador e consultava um contrato na blockchain Polygon para descobrir de onde baixar o payload.

Este case está publicado de forma anonimizada, sem nome do cliente e sem domínio. Trata-se de um portal de notícias com nove anos de operação e cerca de 36 mil posts publicados, hospedado em servidor próprio. O pedido original era simples: recuperar o acesso ao painel e consertar um site que estava "desconfigurado".

A auditoria mudou o diagnóstico logo no início. Não era desconfiguração acidental. Era invasão ativa, com um invasor que ainda tinha acesso ao servidor no momento em que começamos a olhar.

Como se descobre que é invasão, e não bug

A primeira etapa foi 100% leitura — nenhuma alteração no servidor até termos o quadro completo e o backup pronto. Isso importa: em ambiente comprometido, começar a mexer antes de entender destrói a evidência de que o problema já existia antes de você chegar.

O que a leitura encontrou: uma conta de administrador forjada no banco, criada semanas antes; um plugin backdoor ativo na lista de plugins do WordPress; cópias do mesmo código soltas fora da pasta de plugins, nomeadas com timestamp; e dezenas de pastas com nomes genéricos plausíveis — analytics, security, slider, widget — todas criadas na mesma janela de uma hora, sinal claro de ataque automatizado em massa.

Uma dessas cópias tinha sido criada na madrugada do próprio dia da auditoria. O acesso do invasor não era histórico: era corrente.

A causa raiz estava no log, não no palpite

A hipótese inicial mais óbvia era falha conhecida em um plugin de gerenciamento de arquivos instalado no site. Ela foi descartada por verificação: a versão instalada era atual e a vulnerabilidade conhecida daquele plugin tinha sido corrigida anos antes.

A resposta real veio do log de acesso do servidor. Num intervalo de segundos, um mesmo endereço fez várias requisições de login alternando o identificador de navegador entre Chrome, Safari e Firefox — assinatura de script automatizado — e recebeu resposta de redirecionamento de login bem-sucedido mais de uma vez. Entrada por força bruta direto na tela de login do WordPress, contra um site que não tinha nenhum limite de tentativas ativo.

Descartar a hipótese conveniente por verificação, e não por intuição, é o que separa remediar de apenas limpar o que está visível.

O achado mais grave: um backdoor que se escondia do administrador

Entre os artefatos havia um plugin falso com nome imitando um dos plugins de segurança mais populares do WordPress. A escolha do nome não é acidente: num painel com dezenas de plugins, um item chamado "segurança" é o último em que alguém clica para investigar.

Esse plugin injetava um script ofuscado em base64 com XOR nas páginas do site. E fazia uma coisa que vale destacar: antes de injetar, ele verificava se o visitante era administrador logado. Se fosse, o payload era pulado. O dono do site podia navegar pelo próprio portal o dia inteiro sem nunca ver o código malicioso que todos os outros visitantes recebiam.

Decodificado, o script revelou a parte incomum. Ele não tinha o endereço do servidor malicioso embutido. Em vez disso, consultava um smart contract em uma blockchain pública — tentando vários provedores de acesso até um responder — e lia dali de qual domínio deveria carregar o script remoto seguinte.

Por que isso importa

Essa técnica é conhecida como EtherHiding, e ela inverte a lógica de defesa. Num backdoor comum, o endereço do servidor do atacante está no código: você bloqueia o domínio, ou o provedor derruba o servidor, e a infraestrutura maliciosa morre.

Quando o endereço vem de um contrato em blockchain, o atacante pode trocar o destino a qualquer momento, sem nunca mais tocar no site invadido. E o registro em si não pode ser removido por terceiros — não há provedor para notificar, não há host para derrubar. Bloquear por domínio ou IP vira um jogo que a defesa não ganha. O único caminho é remover o injetor no site.

O plugin foi removido do disco e retirado da lista de plugins ativos no banco.

O link invisível na página inicial

Outro achado explicava um sintoma que o cliente já tinha notado sem saber nomear: a descrição que aparecia no Google e nas redes sociais ao compartilhar a home não era o texto do site.

O conteúdo da página inicial no banco tinha sido substituído por um único link posicionado fora da área visível da tela, com texto âncora de marca de cassino apontando para um domínio externo. Invisível para qualquer visitante humano, perfeitamente legível para buscadores — injeção de link para SEO, usando a autoridade acumulada em nove anos do portal para empurrar um site de terceiro.

Removido, com revisão de todas as páginas do site para confirmar que era caso único.

291 posts de spam, e por que limpar foi a parte difícil

O invasor também tinha publicado centenas de posts de spam de apostas em várias línguas — inglês, russo, polonês, tcheco, turco, azerbaijano. Eles apareciam até no widget de "mais populares" da home, porque o tráfego de bot inflava as métricas do próprio site.

Limpar isso num acervo de 36 mil posts legítimos é onde o trabalho realmente mora. Três armadilhas apareceram, e cada uma exigiu mudar o critério.

Filtrar por data não funcionava: parte dos posts de spam tinha data retroativa falsificada, anterior ao ataque. Filtrar por palavra-chave também não bastava: as levas seguintes vinham em cirílico, em grafia polonesa, com nomes de marca que não continham nenhum dos termos buscados. E termos genéricos geravam falso-positivo em conteúdo real — havia artigos legítimos sobre bandas, hotéis e eventos que mencionavam a palavra em contexto verdadeiro.

O critério final foi estrutural, não textual: a faixa de ID sequencial onde a contaminação começou, cruzada com a autoria do post. Toda listagem foi gerada em arquivo e revisada linha a linha antes de qualquer remoção — nunca um filtro alimentando diretamente um comando de exclusão em massa.

Total removido: 291 posts. Zero perda de conteúdo legítimo, confirmada inclusive nos casos ambíguos que mencionavam termos de cassino em contexto real. A verificação final foi refeita imediatamente antes de declarar concluído — e foi exatamente essa recontagem que revelou uma última leva de 32 posts que tinha escapado de todas as checagens anteriores.

Em base grande, "verifiquei há duas horas" não é verificação. A contagem final tem que ser refeita no momento de fechar.

A correção que quase ninguém testa: a proteção protege mesmo?

Removido o malware, sobrava a causa raiz: o login sem limite de tentativas. O site tinha dois plugins de proteção de login instalados e ativos, o que na maioria das auditorias seria registrado como "protegido, item resolvido".

Testamos de verdade. Cinco tentativas de login incorretas em sequência passaram sem nenhum bloqueio. A proteção estava ativa no painel e inerte na prática — ou seja, depois de toda a limpeza, a porta pela qual o invasor entrou continuava exatamente igual.

A correção foi feita uma camada abaixo, direto no servidor web: limite de requisições por endereço na rota de login, com resposta de bloqueio acima do limite. Validado com oito requisições simultâneas, com o bloqueio ocorrendo como esperado e o login legítimo seguindo normal.

Foi registrado um alerta na documentação de entrega: esse arquivo de configuração é gerado por um painel de administração do servidor e pode ser sobrescrito se alguém o regenerar pela interface. Backup do arquivo salvo e a ressalva entregue por escrito, porque uma proteção que pode sumir sem aviso precisa estar documentada.

O que mais foi feito, e o que ficou em aberto

Além da remediação: conta de administrador forjada removida, senhas de banco e de painel rotacionadas, permissões de arquivo corrigidas no servidor — inclusive um arquivo de configuração com permissão de escrita para qualquer processo — e cerca de 7,5 GB de backups antigos abandonados removidos para aliviar o disco, que estava em 95% de uso.

Ajustes de capacidade no PHP e no banco resolveram imagens que não carregavam e reduziram o tempo de carregamento da home pela metade. Não resolveram tudo: o servidor segue subdimensionado para o tráfego real do portal, e isso foi comunicado como decisão de capacidade a tomar, não como item entregue.

Ficou uma pendência honesta: um trecho de texto inerte ainda aparece uma vez no rodapé da home. Não é link, não é script, não redireciona e não coleta nada. A investigação não localizou a origem — não está em nenhum arquivo do site, não está em nenhuma coluna de texto do banco, não é cache e não é reescrita do servidor web. Foi registrado como item de fase seguinte, com scanner dedicado, em vez de ser declarado resolvido.

O que esse tipo de projeto ensina

Um site invadido raramente tem um problema. Tem uma cadeia: uma porta aberta, um invasor que entrou por ela, a persistência que ele instalou para voltar, e o dano que ele monetizou. Remover o malware sem fechar a porta é convite para a segunda visita.

E vale a nota sobre responsabilidade: quando um site com cadastro de usuários é comprometido, a avaliação sobre dever de comunicação aos titulares e à autoridade de proteção de dados é do controlador — o dono do site. Nosso papel é informar o achado com clareza e por escrito, documentar o que foi encontrado e quando, e não decidir isso no lugar dele.

Escopo e stack

WordPress · PHP · MySQL · nginx · Linux · WP-CLI · análise de log de acesso · rate limiting no servidor web · rotação de credenciais · correção de permissões