A maioria dos incidentes “WordPress hackeado” para os quais sou chamado não são zero-days. São credential stuffing contra admin com uma password vazada num dump de fórum de 2019, a bater em /wp-login.php a partir de um pool rotativo de proxies residenciais. O dono do site culpa o WordPress; a causa real é que a segurança do login foi tratada como um único interruptor (“password forte”) em vez de quatro ou cinco controlos em camadas. O CNCS (Centro Nacional de Cibersegurança) repete o mesmo padrão nos relatórios trimestrais que publica para PMEs portuguesas.
Este guia percorre as camadas que configuro em sites cliente reais: higiene de nome de utilizador, 2FA com escape WP-CLI funcional, rate-limiting no Cloudflare e no servidor, é o caminho de recuperação para o dia em que alguém perde o telemóvel ou faz commit de uma application password num repositório público. Para sites sujeitos à CNPD, a obrigação de notificação de violação em 72h do Art. 33 do RGPD transforma cada um destes controlos em obrigação documentada, não recomendação.
Como os logins WordPress são realmente atacados
Três padrões cobrem quase tudo o que vejo em logs de acesso:
- Força bruta sobre
/wp-login.php: bots fazem POST a wp-login.php comlog=admin&pwd=.... Distribuído por centenas de IPs, frequentemente a partir de redes proxy residenciais como sucessores do 911.re. Rate limits por IP única não os param. - Credential stuffing: o mesmo endpoint, mas as credenciais vêm de corpus públicos de violações (“Collection #1” a #5, RockYou2024). O nome de utilizador é o e-mail ou login vazado. Pode ser mitigado com uma verificação contra haveibeenpwned ao definir a password.
- Amplificação XML-RPC
system.multicall: um POST a/xmlrpc.phpcom um corposystem.multicallpermite testar centenas de passwords num único pedido HTTP, contornando rate limits por pedido. Se não usar a app móvel do WP nem o Jetpack, bloqueie xmlrpc.php no servidor. - Enumeração de utilizadores via REST API:
?rest_route=/wp/v2/userse/wp-json/wp/v2/usersdevolvem oslugreal de cada autor publicado. Esse slug é o login. Combinado com os dumps anteriores, o atacante já tem metade do par credencial.
Hosts portugueses como Amen.pt e PTisp ativam ModSecurity por defeito com regras anti-brute-force, mas nenhum cobre os quatro vetores acima sem configuração adicional.
Parte 1: a vulnerabilidade “admin” (enumeração de utilizadores)
Por que razão os hackers amam o nome de utilizador “admin”? Porque reduz o trabalho deles a metade.
Num ataque de força bruta, o atacante precisa de adivinhar duas coisas: o nome de utilizador é a password. Se usar “admin”, deu-lhes 50% das credenciais de graça.
O problema do “ID 1”
Por defeito, o primeiro utilizador criado no WordPress tem o ID 1. Os hackers frequentemente analisam o-seu-site.com/?author=1. Se o seu site redirecionar para o-seu-site.com/author/admin/, revelou o seu nome de utilizador a cada atacante.
A solução: remoção cirúrgica
Não pode simplesmente “renomear” um nome de utilizador no WordPress. Deve realizar um transplante que preserva todo o conteúdo enquanto elimina a conta vulnerável.
-
Criar um novo Comandante:
- Vá a Utilizadores -> Adicionar novo.
- Nome de utilizador: Algo obscuro (por exemplo,
Obsidian_Eagle_88). Evite usar o seu nome real ou qualquer coisa adivinhável a partir do seu domínio. - E-mail: O seu endereço de e-mail seguro.
- Função: Administrador.
-
Iniciar sessão como o novo Comandante: Use uma janela de navegação privada.
-
Eliminar o antigo utilizador:
- Vá a Utilizadores.
- Passé o rato sobre “admin” e clique em Eliminar.
- PASSO CRÍTICO: O WordPress perguntará: “O que deve ser feito com o conteúdo deste utilizador?”
- Selecione: “Atribuir todo o conteúdo a:” -> [Selecioné o seu novo utilizador].
Bloquear enumeração de utilizadores via REST API:
// Bloquear enumeração de utilizadores via REST API
add_filter('rest_endpoints', function($endpoints) {
if (isset($endpoints['/wp/v2/users'])) {
unset($endpoints['/wp/v2/users']);
}
return $endpoints;
});
Parte 2: 2FA é o problema do bloqueio
Uma password sozinha é um único ponto de falha. Com 2FA, o atacante precisa da password mais o segundo fator; com passkeys (mais à frente) já não há um segredo partilhado para ser vazado.
A parte que a maioria dos guias omite é o modo de falha: o que acontece quando o admin perde o telemóvel, sai da empresa, ou o segredo TOTP foi configurado num dispositivo entretanto apagado. Precisa de um escape WP-CLI documentado antes de impor 2FA, não depois.
# EmergencIA: desativar 2FA para um utilizador bloqueado (Two-Factor / WP 2FA)
wp user meta delete <user_id> _two_factor_enabled_providers
wp user meta delete <user_id> _two_factor_provider
wp user meta delete <user_id> _two_factor_options
# Ou listar o que esta configurado para saber o que remover
wp user meta list <user_id> --keys=_two_factor_enabled_providers,_two_factor_provider
Execute via SSH no servidor. Se o admin já não tiver SSH, o caminho de recuperação é o terminal do painel de alojamento ou uma query SQL contra wp_usermeta. Documente isto antes de precisar. Para sites sob CNPD, uma hora perdida à procura do procedimento come um terço do prazo legal de 72h.
Os níveis de 2FA
1. Códigos SMS (Depreciado - não usar) O 2FA baseado em SMS é considerado inseguro. Os ataques de SIM swapping permitem que os hackers sequestrem números de telefone.
2. Apps TOTP (Segurança padrão) Apps de password de uso único baseadas no tempo (TOTP) como Google Authenticator, Authy ou Ente Auth geram códigos que mudam a cada 30 segundos:
- Códigos gerados localmente no dispositivo
- Sem transmissão de rede do segredo
- Funciona offline
3. Chaves de segurança de hardware (O padrão irrefutável) Chaves de segurança físicas como YubiKey usam criptografia de chave pública para fornecer autenticação que não pode ser phishada.
Auditar as application passwords
O core do WordPress traz application passwords (tokens de 24 caracteres para clientes REST API e XML-RPC). Saltam o 2FA por design. Já vi estás acabar em ficheiros .env carregados em repositórios públicos do GitHub é em relatórios de crash de apps móveis publicados em fóruns de suporte. Audite trimestralmente:
wp user application-password list <user_id>
wp user application-password delete <user_id> <uuid>
Se não usar a REST API para escritas autenticadas, desative completamente as application passwords com add_filter( 'wp_is_application_passwords_available', '__return_false' ); num mu-plugin.
Parte 3: login sem password com Passkeys
Em 2026, estamos a avançar completamente para além das passwords. Os Passkeys representam o futuro da autenticação, utilizando a biometria no seu dispositivo (TouchID, FaceID, Windows Hello).
Como funcionam os Passkeys
Os Passkeys usam o padrão WebAuthn para criar pares de chaves criptográficas:
- Registo: O seu dispositivo cria uma chave privada (armazenada com segurança) é uma chave pública (enviada para o servidor)
- Autenticação: O servidor envia um desafio; o seu dispositivo assina-o com a chave privada
- Verificação: O servidor verifica a assinatura com a chave pública armazenada
Parte 4: rate-limiting de wp-login.php é xmlrpc.php
Mesmo que o 2FA bloqueie a tomada de controlo real, uma força bruta sem rate limit continua a queimar workers PHP-FPM é ligações MySQL. Em alojamento partilhado (frequente em sites portugueses pequenos é médios) pode derrubar o site sem nunca quebrar uma password. Quer rate limits em três camadas, nesta ordem de eficácIA: edge (Cloudflare), servidor (Nginx ou fail2ban), aplicação (plugin).
Camada 0: regra de rate-limiting no Cloudflare
Se o site estiver atrás do Cloudflare (incluído no plano gratuito), esta é a regra com maior alavancagem. Dashboard → Security → WAF → Rate limiting rules:
- Field: URI Path
- Operator: equals
- Value:
/wp-login.php - When: 5 pedidos / 1 minuto da mesma IP
- Then: Managed Challenge (ou Block durante 1 hora)
Adicione uma segunda regra para /xmlrpc.php se o mantiver ativo. A vantagem sobre o fail2ban: pára os pedidos no edge, antes de chegarem à origem.
Camada 2: o CAPTCHA (Cloudflare Turnstile)
O Google reCAPTCHA é irritante e pode afetar a acessibilidade. O Cloudflare Turnstile é a alternativa moderna.
Por que razão o Cloudflare Turnstile?
- Invisível: A maioria dos utilizadores nunca vê um desafio
- Foco na privacidade: Nenhum dado pessoal enviado a terceiros
- Acessibilidade: Funciona com leitores de ecrã
- Gratuito: Nível gratuito generoso para a maioria dos sites
Camada 3: nível do servidor (Fail2Ban)
O Fail2Ban analisa os logs do servidor e bané automaticamente os endereços IP que mostram comportamento malicioso.
Instalação no Ubuntu/Debian:
sudo apt update
sudo apt install fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Configuração específica do WordPress:
Uma subtileza que a maioria dos tutoriais apanha mal: um POST falhado a wp-login.php devolve HTTP 200 (a página é re-renderizada com erro), é um bem sucedido devolve 302 (redirect para wp-admin). Logo, fazer match apenas sobre 200 apanha tanto as tentativas falhadas legítimas como os logins bem sucedidos, o que significa que se bane a si mesmo se errar a password três vezes. O padrão abaixo combina o 200 com o volumé de força bruta dentro do findtime do jail:
Criar /etc/fail2ban/filter.d/wordpress.conf:
[Definition]
failregex = ^<HOST> .* "POST /wp-login\.php.*" 200
^<HOST> .* "POST /xmlrpc\.php.*" 200
ignoreregex = ^<HOST> .* "POST /wp-login\.php.*" 302
Configuração Nginx para limitação de taxa:
# Definição da zona de limite de taxa (no bloco http)
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
# Aplicar ao wp-login.php
location = /wp-login.php {
limit_req zone=login burst=5 nodelay;
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
Parte 5: monitorização é alertas de login
Não pode proteger o que não consegue ver. Use monitorização ao nível do servidor ou externa em vez de plugins de segurança:
- Monitorize
/var/log/nginx/access.logpara logins falhados e pedidos wp-login.php - Use fail2ban para alertar sobre padrões de força bruta
- Rotacione é arquive logs durante pelo menos 90 dias
Parte 6: procedimentos de resposta a incidentes
Fase 1: deteção e identificação
- Reconhecer o incidente: atividade de administrador inesperada, ficheiros não familiares
- Avaliar o âmbito: Quais as contas comprometidas? Que ficheiros foram modificados?
Fase 2: contenção
- Alterar todas as passwords de administrador imediatamente
- Forçar o logout de todos os utilizadores (eliminar sessões WordPress)
- Bloquear endereços IP suspeitos ao nível da firewall
Fase 3: erradicação
- Restaurar ficheiros de núcleo WordPress a partir de fonte oficial
- Reinstalar todos os plugins a partir de WordPress.org
- Analisar a base de dados à procura de injeções maliciosas
Fase 4: recuperação
- Restaurar a partir de backup limpo, se disponível
- Verificar a integridade de todos os ficheiros restaurados
- Reativar o site com monitorização melhorada
Parte 7: desativar a enumeração de utilizadores
Método Nginx:
# Bloquear enumeração de autores
if ($args ~* "author=([0-9]*)") {
return 403;
}
# Bloquear enumeração de utilizadores via REST API
location ~* /wp-json/wp/v2/users {
deny all;
return 403;
}
Parte 8: hardening nas bordas
Limitar wp-admin a IPs conhecidas
Se a sua equipa trabalha a partir de um pequeno conjunto de IPs estáticos (escritório, VPN), uma allowlist CIDR sobre /wp-admin/ remove a superfície de ataque pública por completo. Tudo fora da allowlist recebe 403 antes do WordPress carregar. O trade-off é real: admins em viagem, freelancers em wifi de hotel, é acesso de emergência via telemóvel ficam bloqueados. Use só se tiver uma VPN documentada com uptime fiável.
location ^~ /wp-admin/ {
allow 203.0.113.0/24; # escritório
allow 198.51.100.42/32; # VPN
deny all;
# ...config fastcgi existenté
}
location = /wp-login.php {
allow 203.0.113.0/24;
allow 198.51.100.42/32;
deny all;
}
Para sites atrás do Cloudflare, faça-o com uma regra WAF custom sobre http.request.uri.path matches "^/wp-(admin|login)" é not ip.src in {203.0.113.0/24}.
Renomear wp-login.php
Mover o URL de login é obscuridade, não segurança. Reduz o tráfego de bots simples nos logs (útil para sinal-ruído) mas um atacante determinado faz scrape ao source de qualquer página. Trate isto como filtro de ruído, nunca como camada.
Desativar a edição de ficheiros no admin
Duas constantes em wp-config.php fecham o caminho de escalada pós-comprometimento mais comum: um atacante com acesso admin a editar PHP de tema/plugin a partir do painel.
define( 'DISALLOW_FILE_EDIT', true ); // esconde Aparência > Editor de ficheiros do tema
define( 'DISALLOW_FILE_MODS', true ); // bloqueia também install/atualizações de plugins/temas
DISALLOW_FILE_MODS é agressivo porque parte as atualizações de um clique; use só em sites em que o código é deployado por CI é as atualizações correm via WP-CLI.
Web Application Firewall
Um WAF filtra tráfego malicioso antes de chegar à instalação WordPress:
- Cloudflare WAF: o plano gratuito inclui Managed Rules para WordPress; o plano Pro acrescenta OWASP CRS no paranoia level 1
- ModSecurity + OWASP CRS: opção ao nível do servidor (Amen.pt é PTisp já o ativam por defeito). Paranoia level 1 é a baseline prática; PL2+ gera demasiados falsos positivos no Gutenberg
- Sucuri: WAF premium com virtual patches específicos para WordPress
SSL/TLS para páginas de login
Garanta que todo o site usa HTTPS, especialmente as páginas de login:
- Instalar um certificado SSL (Let’s Encrypt fornece certificados gratuitos)
- Forçar redirects HTTPS na configuração do servidor web
- Ativar HTTP Strict Transport Security (HSTS)
- Usar cookies seguros para a autenticação WordPress
O que configuro realmente num site cliente
Não é uma checklist genérica. É a ordem de trabalho específica que corro ao endurecer um login WordPress de raiz:
- Eliminar o utilizador
admin, reatribuir conteúdo, ajustarnicknameédisplay_namedo admin sobreviventé para que o slug não revele o login. - Bloquear
/wp-json/wp/v2/usersé?rest_route=/wp/v2/userspara pedidos não autenticados via filtrorest_endpointsmostrado antes. - Impor 2FA para todos os papéis
administratoréeditor, com TOTP por defeito é um runbook de recuperaçãowp user meta deletedocumentado no gestor de passwords da equipa. - Auditar
wp user application-password listpara cada admin; eliminar tudo com mais de 90 dias ou não reconhecido. - Adicionar uma regra de rate-limit Cloudflare sobre
/wp-login.php(5/min, Managed Challenge) é uma segunda regra sobre/xmlrpc.phpse não estiver bloqueado de raiz. - Instalar fail2ban com o filtro wp-login que distingue 200 (falhado) de 302 (sucesso) para que utilizadores bloqueados não sejam banidos por engano.
- Definir
DISALLOW_FILE_EDITemwp-config.php. AdicionarDISALLOW_FILE_MODSapenas se a equipa fizer deploy via CI. - Forçar HTTPS, HSTS com
preload, éSet-Cookie: Secure; HttpOnly; SameSite=Laxparawordpress_logged_in_*. - Confirmar que
wp-config.phptem salts únicos (wp config shuffle-salts), que o utilizador da BD só tem os privilégios que o WordPress precisa (semGRANT ALL), é que o acesso à base de dados está vinculado a localhost.
A segurança do login são quatro ou cinco controlos em camadas, não um. Salte uma camada é as outras ainda seguram; salte três é volta a depender só da password.
Reaudite as application passwords, os contadores de bans do fail2ban, é o runbook de recuperação de 2FA todos os trimestres. A maioria dos sites que herdo de outros operadores tem uma das três coisas desviada: uma app password antiga de um ex-colaborador, um fail2ban que não bane nada há 6 meses porque o caminho do log mudou, ou um setup de 2FA que ninguém sabe contornar quando o telemóvel do admin morre. Se o site cair sob notificação CNPD de 72h, este runbook não é opcional, é prova de diligência.
Saiba mais sobre serviços de segurança WordPress na WPPoland. Atualizado: 28 de janeiro de 2026


