Pular para o conteúdo
Segurança

O backdoor que sobreviveu à limpeza de malware: 14 mil pastas e um falso acme-challenge

Uma conta de hospedagem compartilhada foi reinfectada em menos de 30 segundos, cinco dias depois de uma limpeza completa. O caminho de volta estava onde nenhuma varredura olhava.

Long aisle of archive shelves filled with bound ledgers, in black and white

Em resumo: quando um site WordPress é reinfectado poucos dias depois de uma limpeza, quase sempre ficou para trás uma porta de entrada. Neste caso, eram quatro shells PHP escondidos em uma árvore de cerca de 14 mil pastas que imitava um caminho de validação do Let’s Encrypt. As varreduras por padrão passaram direto. Ler os logs de acesso no minuto exato da reinfecção mostrou por onde o invasor voltou.

O cenário

Uma conta de hospedagem compartilhada com 16 instalações de WordPress e uns 40 outros sites, todos rodando com o mesmo usuário do sistema. Esse último detalhe importa: numa conta compartilhada, um shell em qualquer site consegue escrever em todos os outros. Um site limpo ao lado de um infectado não fica limpo por muito tempo.

Linha do tempo

  • 4 e 5 de outubro: o invasor cria uma pasta chamada well-known/acme-challenge (sem o ponto no início) dentro de um site e a enche de pastas chamadas a a h, com quatro níveis de profundidade. Mais de 9 mil pastas em uma cópia, 4.600 em uma segunda cópia dentro de wp-content. Quatro delas guardam shells PHP.
  • 7 de outubro: uma limpeza grande. 408 pastas de web shell removidas na conta, administradores falsos apagados, núcleo do WordPress reinstalado. O labirinto fica, porque nada nele batia com o que estávamos procurando.
  • 7 de outubro, 15:44: novos administradores falsos aparecem em três sites, poucas horas depois da limpeza.
  • 12 de outubro, 09:37:45: uma requisição de um IP externo para um shell dentro do labirinto.
  • 09:38:01 a 09:38:27: o servidor começa a chamar shells recém-gravados em cerca de 50 sites, cada um com um parâmetro ?pass= para confirmar que funciona. A conta inteira é reinfectada em menos de 30 segundos.

O que o invasor deixou

  • Cadeias de três pastas com nomes aleatórios de sete caracteres, cada uma com um index.php ofuscado, um tool.php de 220 KB e um php.ini, todos marcados como somente leitura.
  • Trinta arquivos .htaccess idênticos dentro de wp-content e mu-plugins que liberam explicitamente a execução de PHP ali.
  • Um gerenciador de arquivos web na raiz de um site, com nome aleatório.
  • O wp-login.php desse mesmo site renomeado, para o dono não conseguir entrar.
  • Um script PHP de envio de e-mails para disparar spam a partir da conta.
  • Dezesseis contas de administrador falsas em cinco sites, e mais de 200 posts de spam de apostas em um deles, publicados em quatro idiomas.

Por que a primeira limpeza não pegou

A primeira varredura procurou o que já tínhamos visto: as cadeias de pastas de sete caracteres, nomes de arquivos conhecidos, assinaturas de código conhecidas. O labirinto não batia com nada disso. As pastas tinham nomes de uma letra e o caminho parecia algo criado por uma ferramenta de certificado. Ficou quieto por uma semana e foi usado uma única vez.

A lição é procurar por comportamento, não por nome:

  • Arquivos PHP em pastas que nunca deveriam ter PHP (uploads, upgrade, qualquer coisa que imite um .well-known).
  • Arquivos PHP alterados recentemente em qualquer lugar fora das pastas do núcleo, de plugins e de temas.
  • Requisições no log de acesso para arquivos .php fora de wp-admin, wp-includes e dos plugins e temas ativos.

A ordem de limpeza que funciona numa conta compartilhada

  1. Neutralize tudo de uma vez. Movemos 340 pastas de shell e os dois labirintos para uma quarentena fora da raiz web em uma única passada. Limpar um site por vez deixa os outros reinfectarem o que já foi limpo.
  2. Remova os arquivos que reativam o PHP. Os 30 .htaccess maliciosos eram idênticos byte a byte, então comparar hashes encontrou todas as cópias.
  3. Bloqueie PHP onde ele nunca deveria estar. Em wp-content/uploads e wp-content/upgrade de todos os sites:
    <FilesMatch "\.(php|phtml|php[0-9]|phar)$">
        Require all denied
    </FilesMatch>

    Teste: um arquivo PHP colocado ali precisa ser recusado, enquanto as imagens continuam carregando.

  4. Confira o núcleo do WordPress contra os checksums oficiais de api.wordpress.org. Aqui as 16 instalações estavam íntegras, o que mostrou que o invasor agia de fora do núcleo.
  5. Remova os administradores falsos. Os nossos tinham nomes como admin_ seguido de dez caracteres hexadecimais, logins hexadecimais aleatórios, um endereço [email protected] e datas de cadastro em 1970. Guarde uma cópia de cada registro antes de apagar.
  6. Troque todas as credenciais que o invasor pode ter lido: senhas dos bancos de dados e salts de todos os sites, a senha do painel da hospedagem e as contas de FTP.
  7. Faça uma nova varredura depois de algumas horas de tráfego real e execuções de cron. É ali que a reinfecção aparece, não na varredura feita logo depois da limpeza.

Perguntas que recebemos sobre isso

Por que os shells voltam depois que eu apago?

Porque ainda existe pelo menos uma porta de entrada: um shell que você não encontrou, uma conta de administrador que não foi removida ou uma senha que vazou. Apagar os arquivos visíveis sem fechar o caminho de volta só reinicia o relógio.

Um plugin de segurança resolve?

Ajuda com assinaturas conhecidas. Ele não enxerga um shell que roda numa pasta que o plugin não verifica, e não protege um site de outro site na mesma conta de hospedagem.

Preciso trocar a senha do banco se ninguém fez login?

Sim. Um shell consegue ler o wp-config.php sem fazer login em nada. Se um shell rodou na sua conta, considere que as credenciais do banco foram copiadas.

Algo quebrado
no seu site?

Conte o que está acontecendo. Você recebe uma resposta clara e por escrito em até um dia útil.

Fale conosco