La mayoria de incidentes “WordPress hackeado” que veo en clientes españoles no son zero-days. Son credential stuffing contra admin con una password filtrada de un volcado de 2019, golpeando /wp-login.php desde un pool rotativo de proxies residenciales. El propietario culpa a WordPress; la causa real es que la seguridad de login se trato como un único interruptor (“password fuerte”) en lugar de cuatro o cinco controles en capas. Hispasec y los reportes de attaque.es repiten el mismo patrón cada trimestre.
Esta guía recorre las capas que configuro en sitios cliente reales: higiene de nombre de usuario, 2FA con escape WP-CLI funcional, rate-limiting en Cloudflare y servidor, y la ruta de recuperacion para el dia que alguien pierde el telefono o sube una application password a un repo público. Para sitios sujetos a la AEPD, la notificación de brecha en 72h del Art. 33 RGPD convierte cada uno de estos controles en una obligación documentada, no una recomendación.
Como atacan realmente a los logins de WordPress
Tres patrones cubren casi todo lo que veo en logs de acceso:
- Fuerza bruta sobre
/wp-login.php: bots POST a wp-login.php conlog=admin&pwd=.... Distribuido entre cientos de IPs, frecuentementé desde redes proxy residenciales como sucesores de 911.re. Los rate limits por IP unica no los detienen. - Credential stuffing: el mismo endpoint, pero las credenciales vienen de corpus públicos de brechas (las “Collection #1” a #5, RockYou2024). El nombre de usuario es el email o login filtrado. Se puede mitigar con un check contra haveibeenpwned al fijar la password.
- Amplificación XML-RPC
system.multicall: un POST a/xmlrpc.phpcon un cuerposystem.multicallpermite probar cientos de passwords en una única petición HTTP, esquivando rate limits por petición. Si no usa la app movil de WP ni Jetpack, bloquee xmlrpc.php en el servidor. - Enumeracion de usuarios via REST API:
?rest_route=/wp/v2/usersy/wp-json/wp/v2/usersdevuelven elslugreal de cada autor publicado. Ese slug es el login. Combinado con los volcados anteriores, el atacante ya tiene la mitad del par credencial.
Los hosts españoles más serios (WP Engine ES, Raiola Networks) traen ModSecurity activo por defecto con reglas básicas anti-brute-force, pero ninguno de ellos cubre los cuatro vectores anteriores sin configuración adicional.
Parte 1: La Vulnerabilidad “admin” (Enumeracion de Usuarios)
Por que los hackers aman el nombre de usuario “admin”? Porque reduce su trabajo a la mitad.
En un ataque de fuerza bruta, el atacante necesita adivinar dos cosas: el Nombre de usuario y la Contrasena. Si usas “admin”, les diste el 50% de las credenciales gratis. Por eso eliminar nombres de usuario por defecto es el primer y más crítico paso para asegurar tu login de WordPress.
El Problema del “ID 1”
Por defecto, el primer usuario creado en WordPress tiene el ID 1. Los hackers a menudo escanean tu-sitio.com/?author=1. Si tu sitio redirige a tu-sitio.com/author/admin/, acabas de revelar tu nombre de usuario a cada atacante que sabe donde mirar.
La Solución: Eliminacion Quirurgica
No puedes simplemente “renombrar” un nombre de usuario en WordPress. Debes realizar un transplante que preserve todo el contenido mientras eliminas la cuenta vulnerable.
-
Crear un Nuevo Comandante:
- Ve a Usuarios -> Anadir Nuevo.
- Nombre de usuario: Algo oscuro (por ejemplo,
Aguila_Obsidiana_88). Evita usar tu nombre real, prefijo de email o cualquier cosa adivinable desde tu dominio. - Email: Tu dirección de email segura.
- Rol: Administrador.
- Genera una contrasena fuerte usando un gestor de contrasenas.
-
Iniciar Sesion como el Nuevo Comandante: Usa una ventana de navegador privada para asegurarte de no estar autenticado como el usuario antiguo.
-
Eliminar al Recluta:
- Ve a Usuarios.
- Pasa el cursor sobre “admin” y haz clic en Eliminar.
- PASO Crítico: WordPress preguntara: “Que deberia hacerse con el contenido de este usuario?”
- Selecciona: “Atribuir todo el contenido a:” -> [Selecciona tu Nuevo Usuario].
-
Confirmar. “Admin” esta muerto. Larga vida a
Aguila_Obsidiana_88.
Protecciones Adicionales contra Enumeracion de Usuarios
Mas alla de eliminar el usuario admin, implementa estas medidas adicionales:
Deshabilitar Archivos de Autor: Si no necesitas páginas de autor, desactivalas via configuración del servidor o agregando esto a tu functions.php:
// Deshabilitar archivos de autor para prevenir enumeracion de usuarios
add_action('template_redirect', function() {
if (is_author()) {
wp_redirect(home_url(), 301);
exit;
}
});
Bloquear Enumeracion de Usuarios via REST API: La REST API de WordPress expone datos de usuario por defecto. Bloquea el acceso no autorizado:
// Bloquear enumeracion de usuarios via REST API
add_filter('rest_endpoints', function($endpoints) {
if (isset($endpoints['/wp/v2/users'])) {
unset($endpoints['/wp/v2/users']);
}
if (isset($endpoints['/wp/v2/users/(?P<id>[\d]+)'])) {
unset($endpoints['/wp/v2/users/(?P<id>[\d]+)']);
}
return $endpoints;
});
Parte 2: 2FA y el problema del bloqueo
Una password sola es un único punto de fallo. Con 2FA, el atacante necesita la password más el segundo factor; con passkeys (más abajo) ya no hay un secreto compartido que filtrar.
La parte que la mayoría de guías omiten es el modo de fallo: que pasa cuando el admin pierde el teléfono, abandona la empresa, o el secreto TOTP se configuro en un dispositivo que se borro. Necesita un escape WP-CLI documentado antes de imponer 2FA, no después.
# Emergencia: deshabilitar 2FA para un usuario 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
# O listar lo que esta configurado para saber qué eliminar
wp user meta list <user_id> --keys=_two_factor_enabled_providers,_two_factor_provider
Ejecútelo desde SSH en el servidor. Si el admin tampoco tiene SSH, la ruta de recuperación es la terminal del panel del hosting o una consulta SQL contra wp_usermeta. Documéntelo antes de necesitarlo, especialmente si el sitio cae bajo la obligación de notificación 72h de la AEPD: una hora perdida buscando el procedimiento se come un cuarto del plazo legal.
Los Niveles de 2FA
1. Códigos SMS (Obsoleto - No Usar) El 2FA basado en SMS era comun pero ahora se considera inseguro. Los ataques de SIM swapping permiten a los hackers secuestrar números de telefono.
2. Códigos por Email (Debil pero Mejor que Nada) El 2FA basado en email envia un código único a tu dirección de email registrada. Vulnerable si tu cuenta de email esta comprometida.
3. Aplicaciones TOTP (Seguridad Estándar) Las aplicaciones de Contrasena de Un Solo Uso Basada en Tiempo (TOTP) como Google Authenticator, Authy, Microsoft Authenticator y Ente Auth generan códigos que cambian cada 30 segundos. Son significativamente más seguras porque los códigos se generan localmente en tu dispositivo.
4. Notificaciones Push (Conveniente pero Dependiente del Proveedor) Servicios como Duo Security envian notificaciones push a tu telefono para aprobacion.
5. Llaves de Seguridad por Hardware (El Estándar Blindado) Las llaves fisicas de seguridad como YubiKey, Titan Security Key y dispositivos compatibles con FIDO2 representan el estándar de oro en autenticación:
- Anti-phishing: Incluso si escribes tu contrasena en un sitio falso, la autenticación falla porque la llave fisica verifica el dominio
- Prueba criptografica: Usa protocolos de desafio-respuesta que no pueden ser reproducidos
- Sin secretos compartidos: A diferencia de TOTP, no hay secreto que pueda ser robado del servidor
Auditar las application passwords
WordPress core incluye application passwords (tokens de 24 caracteres para clientes REST API y XML-RPC). Saltan el 2FA por diseño. He visto estas terminar en archivos .env subidos a repos públicos de GitHub y en reportes de crash de apps móviles publicados en foros de soporte. Audítelas trimestralmente:
wp user application-password list <user_id>
wp user application-password delete <user_id> <uuid>
Si no usa la REST API para escrituras autenticadas, deshabilítelas completamente con add_filter( 'wp_is_application_passwords_available', '__return_false' ); en un mu-plugin.
Parte 3: Login sin Contrasena con Passkeys
En 2026, estamos avanzando más alla de las contrasenas. Las Passkeys representan el futuro de la autenticación, utilizando la biometria de tu dispositivo (TouchID, FaceID, Windows Hello) para autenticar via criptografia de clave pública.
Como Funcionan las Passkeys
Las Passkeys usan el estándar WebAuthn para crear pares de claves criptograficas:
- Registro: Tu dispositivo crea una clave privada (almacenada de forma segura) y una clave pública (enviada al servidor)
- Autenticación: El servidor envia un desafio; tu dispositivo lo firma con la clave privada
- Verificación: El servidor verifica la firma con la clave pública almacenada
Ventajas de las Passkeys
- Sin contrasena que robar: No hay secreto compartido que pueda filtrarse
- Resistente al phishing: La clave privada esta vinculada al dominio específico
- Sincronizacion entre dispositivos: Las Passkeys se sincronizan a través de tu Apple ID, cuenta de Google o gestor de contrasenas
- Conveniencia biometrica: Usa huella dactilar o reconocimiento facial en lugar de escribir contrasenas
Parte 4: Rate-limiting de wp-login.php y xmlrpc.php
Aunque el 2FA bloquee la toma de control real, una fuerza bruta sin rate limit sigue quemando workers PHP-FPM y conexiones MySQL. En hosting compartido (frecuente en sitios pequeños españoles) puede tirar el sitio sin descifrar nunca una password. Quiere rate limits en tres capas, en este orden de eficacia: edge (Cloudflare), servidor (Nginx o fail2ban), aplicación (plugin).
Capa 0: regla de rate-limiting en Cloudflare
Si el sitio esta en Cloudflare (incluido en plan gratuito), esta es la regla de mayor apalancamiento. Dashboard → Security → WAF → Rate limiting rules:
- Field: URI Path
- Operator: equals
- Value:
/wp-login.php - When: 5 peticiones / 1 minuto desde la misma IP
- Then: Managed Challenge (o Block durante 1 hora)
Anada una segunda regla para /xmlrpc.php si lo mantiene activo. La ventaja sobre fail2ban: detiene las peticiones en el edge, antes de que lleguen a su origen.
Capa 1: Cloudflare Turnstile
Google reCAPTCHA es molesto y envia datos a los servidores de Google. Cloudflare Turnstile es la alternativa moderna:
- Invisible: La mayoria de los usuarios nunca ven un desafio
- Enfocado en privacidad: Sin datos personales enviados a terceros
- Accesibilidad: Funciona con lectores de pantalla y tecnologías asistivas
- Rendimiento: Impacto minimo en los tiempos de carga
Capa 2: Fail2Ban a Nivel de Servidor
Fail2Ban escanea los logs de tu servidor y automáticamente banea direcciones IP que muestran comportamiento malicioso.
Instalación en Ubuntu/Debian:
sudo apt update
sudo apt install fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Configuración Específica para WordPress:
Una sutileza que la mayoría de tutoriales falla: un POST fallido a wp-login.php devuelve HTTP 200 (la página se re-renderiza con error), y uno exitoso devuelve 302 (redirección a wp-admin). Por tanto, hacer match solo sobre 200 caza tanto los intentos fallidos legítimos como los logins exitosos, lo que significa que se banea a si mismo si escribe mal su password tres veces. El patrón siguiente combina el 200 con el volumen de fuerza bruta dentro del findtime del jail:
Crear /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
Crear /etc/fail2ban/jail.local:
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
backend = systemd
[wordpress]
enabled = true
port = http,https
filter = wordpress
logpath = /var/log/nginx/access.log
maxretry = 3
bantime = 86400
Configuración de Nginx para Limitacion de Tasa:
# Definicion de zona de limitacion de tasa (en bloque http)
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
# Aplicar a 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;
}
# Bloquear XML-RPC si no se necesita
location = /xmlrpc.php {
deny all;
access_log off;
log_not_found off;
}
Parte 5: Monitoreo y Alertas de Login
No puedes asegurar lo que no puedes ver. El monitoreo integral de login proporciona visibilidad sobre patrones de ataque y alerta temprana de intentos de compromiso.
Que Monitorear
- Intentos de login fallidos: Rastrear por nombre de usuario e IP
- Logins exitosos: Registrar todos los logins de admin exitosos
- Cambios de cuenta: Monitorear cambios de contrasena y modificaciones de rol
- Anomalias geograficas: Marcar logins desde paises inesperados
No recomendamos plugins de seguridad. Usa monitoreo a nivel de servidor e infraestructura:
- Monitor logs de acceso del servidor para logins fallidos y solicitudes a wp-login.php
- Usa fail2ban para alertar sobre patrones de fuerza bruta
- Herramientas externas como Loggly, Splunk o Datadog para sitios de alto valor
Parte 6: Procedimientos de Respuesta a Incidentes
A pesar de los mejores esfuerzos, los incidentes de seguridad ocurren. Tener procedimientos documentados asegura una respuesta rápida y efectiva.
Plantilla del Plan de Respuesta a Incidentes
Fase 1: Deteccion e Identificacion
- Actividad de admin inesperada
- Archivos desconocidos en directorios de WordPress
- Picos repentinos de tráfico
- Advertencias de Google Safe Browsing
Fase 2: Contencion
- Cambiar todas las contrasenas de admin inmediatamente
- Forzar cierre de sesion de todos los usuarios
- Habilitar modo mantenimiento si es necesario
- Bloquear direcciones IP sospechosas a nivel de firewall
Fase 3: Erradicacion
- Restaurar archivos core de WordPress desde fuente oficial
- Reinstalar todos los plugins desde WordPress.org
- Escanear base de datos en busca de inyecciones maliciosas
Fase 4: Recuperacion
- Restaurar desde respaldo limpio si esta disponible
- Verificar integridad de todos los archivos restaurados
- Reactivar sitio con monitoreo mejorado
Fase 5: Revision Post-Incidente
- Analizar el vector de ataque
- Documentar lecciones aprendidas
- Actualizar procedimientos de seguridad
Parte 7: Deshabilitar “Enumeracion de Usuarios”
Los hackers usan scripts automatizados para adivinar nombres de usuario escaneando /?author=1, /?author=2, etc. Puedes bloquear esto a nivel de servidor.
Método Apache (.htaccess)
## Detener escaneo de autores
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{QUERY_STRING} (author=\d+) [NC]
RewriteRule .* - [F]
</IfModule>
Método Nginx
# Bloquear enumeracion de autores
if ($args ~* "author=([0-9]*)") {
return 403;
}
# Bloquear enumeracion de usuarios via REST API
location ~* /wp-json/wp/v2/users {
deny all;
return 403;
}
Parte 8: Hardening en los bordes
Limitar wp-admin a IPs conocidas
Si su equipo trabaja desde un conjunto pequeño de IPs estáticas (oficina, VPN), una allowlist CIDR sobre /wp-admin/ elimina la superficie de ataque pública por completo. Cualquier cosa fuera de la allowlist recibe un 403 antes de que WordPress se cargue. La contrapartida es real: administradores en viaje, freelancers en wifi de hotel, y acceso de emergencia desde un movil quedan rotos. Úselo solo si tiene una VPN documentada con uptime fiable.
location ^~ /wp-admin/ {
allow 203.0.113.0/24; # oficina
allow 198.51.100.42/32; # VPN
deny all;
# ...config fastcgi existente
}
location = /wp-login.php {
allow 203.0.113.0/24;
allow 198.51.100.42/32;
deny all;
}
Para sitios detrás de Cloudflare, hagálo con una WAF custom rule sobre http.request.uri.path matches "^/wp-(admin|login)" y not ip.src in {203.0.113.0/24}.
Renombrar wp-login.php
Mover la URL de login es oscuridad, no seguridad. Reduce el tráfico de bots tontos en sus logs (útil para señal-ruido) pero un atacante determinado lo extrae del source de cualquier página. Trátelo como filtro de ruido, nunca como capa.
Deshabilitar la edición de archivos en admin
Dos constantes en wp-config.php cierran la ruta de escalada post-compromiso más común: un atacante con acceso admin editando PHP de tema/plugin desde el panel.
define( 'DISALLOW_FILE_EDIT', true ); // oculta Apariencia > Editor de archivos del tema
define( 'DISALLOW_FILE_MODS', true ); // tambien bloquea instalación/actualizaciones de plugins/temas
DISALLOW_FILE_MODS es agresivo porque rompe las actualizaciones de un clic; úselo en sitios donde despliega código por CI y ejecuta updates via WP-CLI.
Web Application Firewall
Un WAF filtra tráfico malicioso antes de que llegue a su instalación WordPress:
- Cloudflare WAF: el plan gratuito incluye Managed Rules para WordPress; el plan Pro añade OWASP CRS en paranoia level 1
- ModSecurity + OWASP CRS: opción a nivel de servidor (Raiola Networks y otros hosts españoles ya lo activan por defecto). Paranoia level 1 es la base práctica; PL2+ genera demasiados falsos positivos en Gutenberg
- Sucuri: WAF premium con virtual patches específicos para WordPress
SSL/TLS para Páginas de Login
Asegura que todo tu sitio use HTTPS, especialmente las páginas de login:
- Instalar un certificado SSL (Let’s Encrypt proporciona certificados gratuitos)
- Forzar redirecciones HTTPS en la configuración del servidor web
- Habilitar HTTP Strict Transport Security (HSTS)
- Usar cookies seguras para la autenticación de WordPress
Lo que configuro realmente en un sitio cliente
No es una checklist genérica. Es la orden de trabajo específica que ejecuto al endurecer un login WordPress desde cero:
- Eliminar el usuario
admin, reatribuir contenido, ajustarnicknameydisplay_namedel admin superviviente para que el slug no filtre el login. - Bloquear
/wp-json/wp/v2/usersy?rest_route=/wp/v2/userspara peticiones no autenticadas via el filtrorest_endpointsmostrado antes. - Imponer 2FA para todos los roles
administratoryeditor, con TOTP por defecto y un runbook de recuperaciónwp user meta deletedocumentado en el gestor de passwords del equipo. - Auditar
wp user application-password listpara cada admin; eliminar cualquier app password con más de 90 días o no reconocido. - Anadir una regla de rate-limit en Cloudflare sobre
/wp-login.php(5/min, Managed Challenge) y una segunda regla sobre/xmlrpc.phpsi no esta bloqueado de raíz. - Instalar fail2ban con el filtro wp-login que distingue 200 (fallido) de 302 (éxito) para que los usuarios bloqueados no sean baneados por error.
- Fijar
DISALLOW_FILE_EDITenwp-config.php. AnadirDISALLOW_FILE_MODSsolo si el equipo despliega via CI. - Forzar HTTPS, HSTS con
preload, ySet-Cookie: Secure; HttpOnly; SameSite=Laxparawordpress_logged_in_*. - Verificar que
wp-config.phptiene salts únicos (wp config shuffle-salts), que el usuario DB tiene solo los privilegios que WordPress necesita (sinGRANT ALL), y que el acceso a la base de datos esta bound a localhost.
La seguridad de login son cuatro o cinco controles en capas, no uno. Salte una capa y el resto aún sostienen; salte tres y vuelve a depender de la password.
Reaudite las application passwords, los contadores de baneos de fail2ban, y el runbook de recuperación de 2FA cada trimestre. La mayoría de sitios que asumo de otros operadores tienen uno de los tres derivado: una app password caducada de un ex-empleado, un fail2ban que no banea nada desde hace 6 meses porque la ruta del log se movió, o un setup de 2FA que nadie sabe cómo saltar cuando el movil del admin muere. Y si el sitio cae bajo notificación AEPD 72h, ese runbook no es opcional: es prueba de diligencia.
Conoce más sobre los servicios de auditoria de seguridad WordPress en WPPoland.
Actualizado: 28 de enero de 2026 Proxima Revision: 28 de abril de 2026

