WordPress hackeado otra vez: encuentre la puerta trasera
¿Su WordPress sigue hackeado tras limpiarlo? Encuentre las puertas traseras que sobreviven: auto_prepend_file, mu-plugins, plugins falsos, cron y sitios vecinos.
También en: English Deutsch Français Italiano Polski Português Türkçe
Si su WordPress vuelve a ser hackeado después de haberlo limpiado, el atacante casi seguro conserva una vía de acceso. O bien una puerta trasera sobrevivió a la limpieza, o bien el fallo que usó la primera vez sigue abierto. Reinstalar el núcleo y borrar unos cuantos archivos no arregla ninguna de las dos cosas.
Esta guía parte de que ya se ha hecho una limpieza. Repasa los lugares donde suelen sobrevivir las puertas traseras, en el orden en que se ejecuta el código, con una comprobación concreta para cada uno. Y termina con el paso que de verdad rompe el ciclo.
Por qué WordPress se vuelve a hackear tras la limpieza
Hay dos causas, y a menudo se dan las dos. La primera es la persistencia. Los atacantes rara vez dejan una sola puerta trasera: plantan varias en sitios distintos para que, si usted encuentra una, las demás sigan funcionando. La segunda es un punto de entrada que sigue abierto: un plugin vulnerable, una contraseña robada o un sitio vecino dentro de la misma cuenta de alojamiento.
El momento de la reinfección da una pista. Si ocurre a las pocas horas de la limpieza, suele apuntar a una puerta trasera superviviente o a una tarea programada que reescribe archivos. Si ocurre a los días o semanas, apunta más bien a una brecha abierta que alguien, o algún bot, ha vuelto a encontrar.
Las brechas abiertas se encuentran rápido. State of WordPress Security in 2026, de Patchstack, es un informe de un proveedor, así que conviene leerlo como tal. Según este informe, en las vulnerabilidades más explotadas la mediana ponderada del tiempo hasta la explotación masiva fue de 5 horas. Cerca de la mitad de las vulnerabilidades de alto impacto se explotaron en 24 horas. El mismo informe señala que el 46 % de las vulnerabilidades no tenía corrección disponible en el momento de su divulgación pública. Un plugin sin parchear no es un riesgo menor que se pueda dejar para la semana que viene.
Antes de buscar: copie primero las pruebas
Haga una copia completa de los archivos y de la base de datos, y guarde los registros de acceso del servidor web, antes de borrar nada más. Después registre las marcas de tiempo de los archivos, para poder cruzar los cambios con las entradas del registro más adelante:
cd /path/to/site
find . -type f -printf '%T@ %TY-%Tm-%Td %TH:%TM %p\n' | sort -n > ~/file-times-$(date +%F).txt
wp db export ~/evidence-db-$(date +%F).sql
La exportación de la base de datos contiene datos personales, así que saque estas copias del servidor y guárdelas en un almacenamiento seguro. Si no, la búsqueda de copias de seguridad que verá más abajo las listará y podrían leerse o borrarse por error. Lleve una lista sencilla, con fecha, de cada archivo, usuario y tarea cron que elimine. La necesitará para localizar el punto de entrada. Y si hay datos personales implicados, también porque el artículo 33, apartado 5, del RGPD obliga a documentar toda violación de la seguridad, incluso las que no se notifican. Los pasos completos de contención están en nuestro plan para eliminar malware de WordPress.
Puertas traseras que se ejecutan antes de que cargue WordPress
El ajuste auto_prepend_file de PHP ejecuta un archivo antes de cada petición PHP, antes de que cargue WordPress y antes de que cualquier plugin de seguridad pueda verlo. Por eso gusta tanto a los atacantes. Puede definirse en php.ini, en un archivo .user.ini de cualquier directorio o con php_value en .htaccess en Apache con mod_php. Busque en toda la cuenta:
grep -Rn --include='*.ini' --include='.htaccess' -E 'auto_(prepend|append)_file' ~ 2>/dev/null
php --ini
php --ini muestra qué archivos de configuración carga el PHP de línea de comandos. El PHP del servidor web puede usar otros, así que revise también los ajustes de PHP en el panel de su hosting. Cualquier valor que apunte a un archivo que no reconozca es un hallazgo. Anote la ruta, guarde una copia de ese archivo y luego elimine el ajuste. Los cambios en .user.ini pueden tardar unos minutos en aplicarse, porque PHP guarda esos archivos en caché.
A continuación, revise wp-config.php. Uno limpio tiene un único require_once ABSPATH . 'wp-settings.php'; cerca del final y nada ejecutable por encima del bloque de comentarios inicial. Busque cualquier otra cosa:
head -n 5 wp-config.php
grep -nE 'eval\(|base64_decode|gzinflate|str_rot13|include|require' wp-config.php
awk 'length > 300 {print FILENAME": line "NR" is "length" chars"}' wp-config.php
Después compárelo con diff frente a una copia en buen estado de una copia de seguridad antigua. Si no tiene ninguna copia limpia, compare su estructura con wp-config-sample.php. Los include adicionales, las líneas larguísimas y el código antes del bloque de comentarios no tienen nada que hacer ahí.
Escondites dentro de wp-content: mu-plugins y plugins falsos
Plugins de uso obligatorio y drop-ins
Todo lo que hay en wp-content/mu-plugins se ejecuta automáticamente en cada petición. WordPress solo los muestra en la pestaña independiente «Imprescindibles» (Must-Use) de la pantalla de plugins, los archivos PHP en subcarpetas no aparecen en absoluto y ninguno puede desactivarse desde el escritorio. Muchos sitios no tienen ningún plugin de este tipo. Algunos proveedores de hosting instalan uno para la caché o la gestión.
ls -la wp-content/mu-plugins
wp plugin list --status=must-use
wp plugin list --status=dropin
Los drop-ins son otro lugar que conviene revisar: object-cache.php, advanced-cache.php y db.php, directamente en wp-content. Son legítimos si los puso allí su plugin de caché o su proveedor. Si nada de lo que usted instaló los explica, trátelos como sospechosos.
Carpetas de plugins falsos o con nombres parecidos
Una carpeta de plugin sin una cabecera de plugin válida no aparece en WordPress, pero sus archivos PHP pueden seguir llamándose directamente por URL. Compare lo que hay en disco con lo que WordPress conoce:
ls -1 wp-content/plugins | sort > on-disk.txt
wp plugin list --field=name | sort > registered.txt
comm -23 on-disk.txt registered.txt
index.php y los plugins de un solo archivo, como hello.php, aparecerán en el resultado. Todo lo demás merece una revisión. Desconfíe de los nombres que imitan a plugins reales: un guion de más, un número de versión en el nombre de la carpeta o algo como «wp-security-core». Después ejecute wp plugin verify-checksums --all. Solo cubre los plugins alojados en WordPress.org. Para los plugins de pago, descargue una copia nueva del proveedor y compárela con diff -r. Para el núcleo de WordPress, siga nuestra guía para verificar las sumas de comprobación de WordPress.
Archivos PHP modificados recientemente
find wp-content -type f -name '*.php' -mtime -30 -printf '%TY-%Tm-%Td %p\n' | sort
find wp-content -type f -name '*.php' -ctime -30
Si todavía no ha reinstalado el núcleo, -newer wp-includes/version.php es otro punto de referencia útil. Los atacantes pueden falsificar las fechas de modificación con touch, pero no pueden falsificar con facilidad la fecha de cambio (-ctime), así que ejecute ambas. Los archivos PHP dentro de uploads se tratan aparte en nuestra guía sobre web shells en wp-content/uploads.
Persistencia en la base de datos y el planificador: cron, administradores, contraseñas de aplicación
Los eventos de WP-Cron viven en la base de datos y sobreviven a una limpieza de archivos. Listelos:
wp cron event list --fields=hook,next_run_relative,recurrence
Cada hook debería pertenecer al núcleo o a un plugin que usted conozca. Si hay un hook desconocido, busque en el código con grep -Rn "hook_name" wp-content. Si el código que lo registra está en un mu-plugin o en un archivo que no sabe explicar, ha encontrado una puerta trasera. Un hook sin código asociado puede significar que la puerta trasera se eliminó pero el evento sigue ahí. Borre el evento con wp cron event delete hook_name.
Después revise la parte del servidor: crontab -l para el usuario de hosting, y la sección de tareas cron de su panel. Una tarea que ejecute curl o wget, o que escriba en la raíz web, puede restaurar una puerta trasera cada noche sin que nadie lo note.
Siguen las cuentas. Este comando lista todos los administradores y, después, las contraseñas de aplicación de cada uno:
wp user list --role=administrator --fields=ID,user_login,user_email,user_registered
for id in $(wp user list --role=administrator --field=ID); do echo "User $id"; wp user application-password list "$id"; done
Las contraseñas de aplicación siguen funcionando después de cambiar la contraseña, así que una entrada desconocida aquí es tan grave como un administrador desconocido. La revisión completa está en nuestra guía sobre roles de usuario de WordPress.
Por último, busque scripts inyectados en el contenido y en los ajustes. Cambie wp_ si su prefijo de tablas es otro:
wp db search 'script src=' wp_options wp_posts --stats
wp db search 'atob(' wp_options wp_posts --stats
wp db query "SELECT option_name, LENGTH(option_value) AS size FROM wp_options WHERE autoload IN ('yes','on','auto','auto-on') ORDER BY size DESC LIMIT 20;"
Las dos primeras búsquedas encuentran inclusiones de scripts externos y la decodificación base64 que suele usar el JavaScript inyectado. La consulta lista las opciones de carga automática más grandes; una opción con nombre desconocido y un valor enorme merece una mirada más atenta.
El plugin de auditoría de secaudit.eu reúne estos indicadores de compromiso en un solo lugar: administradores ocultos, entradas cron sospechosas, spam en la base de datos y discrepancias en las sumas de comprobación del núcleo. Detecta e informa. Las decisiones sobre qué eliminar siguen siendo suyas → secaudit.eu
El resto de la cuenta de alojamiento: sitios vecinos y copias antiguas
El hosting compartido suele ejecutar varios sitios bajo un mismo usuario del sistema. Una copia de staging infectada, una instalación olvidada en /old/ o /test/, o un ZIP de copia de seguridad en la raíz web reinfectarán el sitio que acaba de limpiar. Liste todo lo que hay en la cuenta:
find ~ -name wp-config.php 2>/dev/null
find ~ -type d -name wp-includes 2>/dev/null
find ~ -type f \( -name '*.zip' -o -name '*.tar.gz' -o -name '*.sql' \) 2>/dev/null
Limpie todos los sitios de la cuenta a la vez, o borre los que nadie necesite. Si todos comparten un mismo usuario SFTP y un mismo usuario de base de datos, cualquier sitio comprometido puede escribir en todos los demás. Sepárelos en usuarios distintos o en cuentas de hosting distintas siempre que pueda.
Cierre el punto de entrada y rote todo a la vez
Busque el punto de entrada en los registros de acceso, alrededor de la fecha de la primera infección. Fíjese en las peticiones POST a archivos extraños y a los endpoints del plugin que sospeche:
grep '"POST ' access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -30
Actualice el plugin vulnerable. Si no existe corrección o el plugin está abandonado, sustitúyalo. Si no lo necesita, elimínelo. Desactivarlo no basta: los archivos siguen en disco y se puede acceder a ellos por URL.
Después rote todas las credenciales en una sola sesión, cuando la limpieza haya terminado y no antes. Si rota antes, una puerta trasera superviviente simplemente leerá los valores nuevos.
- Contraseñas de todos los administradores de WordPress y todas las contraseñas de aplicación
- Contraseña de la base de datos: cámbiela en el panel de hosting y actualice después
DB_PASSWORDen wp-config.php - Acceso al panel de hosting, contraseñas SFTP y claves SSH (elimine las que no reconozca)
- Claves de API guardadas en plugins (pagos, SMTP, CDN, almacenamiento de copias de seguridad)
- Claves de seguridad y salts:
wp config shuffle-salts
Repita las mismas comprobaciones a las 24 horas y de nuevo a los 7 días, y compare los resultados con sus archivos de pruebas. Si reaparece un mu-plugin, un evento cron o un archivo modificado, todavía queda algo que no ha encontrado.
Preguntas frecuentes
¿Cuándo debo dejar de hacerlo por mi cuenta?
Pare en la segunda reinfección, o de inmediato si pueden haberse expuesto datos de clientes o si el sitio es una tienda con pedidos. En ese punto, recurra a un servicio profesional de respuesta a incidentes. Si hay datos personales afectados, compruebe además si procede la notificación a la autoridad de control en 72 horas prevista en el RGPD; en España, la autoridad competente es la AEPD. Nuestra guía sobre la notificación de violaciones en 72 horas explica cuándo empieza a contar el plazo.
¿Basta con restaurar una copia de seguridad?
Solo si la copia es anterior a la primera puerta trasera y ya ha cerrado el punto de entrada. De lo contrario, restaura el problema junto con el sitio.
¿Cambiar todas las contraseñas detiene los ataques?
No. Una puerta trasera superviviente no necesita contraseña. Rote las credenciales después de la búsqueda, no en lugar de ella.
Este artículo es información general y no constituye asesoramiento jurídico.
Compruebe su propio sitio
Security Audit & Compliance es el plugin de WordPress de secaudit.eu: busca malware, spam oculto, archivos del núcleo modificados y código vulnerable, y relaciona sus hallazgos con el art. 32 del RGPD, el art. 21 de NIS2 y el OWASP Top 10.
Ver qué comprueba el plugin