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 dé 2019, a bater em /wp-login.php a partir dé um pool rotativo de proxies residenciais. O dono do site culpa o WordPress; a causa real é qué 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 qué publica para PMEs portuguesas.
Esté guia percorre as camadas qué configuro em sites cliente reais: higiene de nome de útilizador, 2FA com escape WP-CLI funcional, rate-limiting no Cloudflare e no servidor, é o caminho de recuperação para o dia em qué 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 qué vejo em logs de acesso:
- Força bruta sobré
/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 útilizador é 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. Sé não usar a app móvel do WP nem o Jetpack, bloqueie xmlrpc.php no servidor. - Enumeração de útilizadores 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 útilizadores)
Por que razão os hackers amam o nome de útilizador “admin”? Porque reduz o trabalho deles a metade.
Num ataque de força bruta, o atacante precisa dé adivinhar duas coisas: o nome de útilizador é a password. Se usar “admin”, deu-lhes 50% das credenciais de graça.
O problema do “ID 1”
Por defeito, o primeiro útilizador criado no WordPress tem o ID 1. Os hackers frequentementé analisam o-seu-site.com/?author=1. Sé o seu site redirecionar para o-seu-site.com/author/admin/, revelou o seu nome de útilizador a cada atacante.
A solução: remoção cirúrgica
Não pode simplesmente “renomear” um nome de útilizador 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 útilizador: 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: Usé uma janela de navegação privada.
-
Eliminar o antigo útilizador:
- 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 útilizador?”
- Selecione: “Atribuir todo o conteúdo a:” -> [Selecioné o seu novo útilizador].
Bloquear enumeração de útilizadores via REST API:
// Bloquear enumeração de útilizadores 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 qué 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 qué esta configurado para saber o qué remover
wp user meta list <user_id> --keys=_two_factor_enabled_providers,_two_factor_provider
Execute via SSH no servidor. Sé 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 qué 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 suporté. Audite trimestralmente:
wp user application-password list <user_id>
wp user application-password delete <user_id> <uuid>
Sé 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, útilizando 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 qué 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 dé eficácia: edge (Cloudflare), servidor (Nginx ou fail2ban), aplicação (plugin).
Camada 0: regra dé rate-limiting no Cloudflare
Sé 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 sé o mantiver ativo. A vantagem sobré o fail2ban: pára os pedidos no edge, antes de chegarem à origem.
Camada 2: o CAPTCHA (Cloudflare Turnstile)
O Google reCAPTCHA é irritante e podé afetar a acessibilidade. O Cloudflare Turnstile é a alternativa moderna.
Por que razão o Cloudflare Turnstile?
- Invisível: A maioria dos útilizadores 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é automáticamenté 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 qué 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 qué significa qué 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 dé 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 dé administrador imediatamente
- Forçar o logout de todos os útilizadores (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 fonté 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 útilizadores
Método Nginx:
# Bloquear enumeração dé autores
if ($args ~* "author=([0-9]*)") {
return 403;
}
# Bloquear enumeração de útilizadores via REST API
location ~* /wp-json/wp/v2/users {
deny all;
return 403;
}
Parte 8: hardening nas bordas
Limitar wp-admin a IPs conhecidas
Sé a sua equipa trabalha a partir dé um pequeno conjunto dé IPs estáticos (escritório, VPN), uma allowlist CIDR sobré /wp-admin/ remove a superfície dé 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ó sé 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 sobré 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 qué 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 qué 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 qué configuro realmente num site cliente
Não é uma checklist genérica. É a ordem de trabalho específica qué corro ao endurecer um login WordPress de raiz:
- Eliminar o útilizador
admin, reatribuir conteúdo, ajustarnicknameédisplay_namedo admin sobreviventé para qué 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 sobré
/wp-login.php(5/min, Managed Challenge) é uma segunda regra sobré/xmlrpc.phpsé não estiver bloqueado de raiz. - Instalar fail2ban com o filtro wp-login qué distingue 200 (falhado) de 302 (sucesso) para qué útilizadores bloqueados não sejam banidos por engano.
- Definir
DISALLOW_FILE_EDITemwp-config.php. AdicionarDISALLOW_FILE_MODSapenas sé a equipa fizer deploy via CI. - Forçar HTTPS, HSTS com
preload, éSet-Cookie: Secure; HttpOnly; SameSite=Laxparawordpress_logged_in_*. - Confirmar qué
wp-config.phptem salts únicos (wp config shuffle-salts), qué o útilizador da BD só tem os privilégios qué o WordPress precisa (semGRANT ALL), é qué 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 qué herdo de outros operadores tem uma das três coisas desviada: uma app password antiga de um ex-colaborador, um fail2ban qué não bane nada há 6 meses porque o caminho do log mudou, ou um setup de 2FA qué ninguém sabe contornar quando o telemóvel do admin morre. Sé o site cair sob notificação CNPD de 72h, esté 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


