PathSentinel Article

Cómo limpiar un WordPress hackeado paso a paso

Procedimiento completo para limpiar a mano un WordPress infectado, paso a paso y en orden, con los puntos exactos donde casi todo el mundo se deja algo dentro.

Si has llegado hasta aquí es porque ya sabes o sospechas que tu WordPress está infectado: redirige a sitios que no son tuyos, Google lo ha marcado, han aparecido archivos que tú no subiste o el hosting te ha mandado un aviso. Abajo tienes el procedimiento de limpieza manual completo, en el orden en el que hay que hacerlo. No necesitas ser programador, pero sí necesitas tres accesos: el panel del hosting (cPanel, Plesk o el que uses), acceso por SFTP y acceso a la base de datos por phpMyAdmin. Si te falta alguno, consíguelo antes de empezar; sin ellos vas a limpiar solo la mitad.

Una advertencia para que no te lleves un chasco a mitad de camino: limpiar una web tiene dos partes. Una es quitar lo que se ve (la redirección, el spam, la página falsa). La otra es quitar lo que el atacante dejó para volver a entrar, y es la que decide si dentro de dos semanas estás otra vez igual. Si aún no tienes claro qué te está pasando, identifica primero el síntoma con la guía general sobre qué hacer cuando tu web está hackeada.

Antes de empezar: copia de seguridad forense y modo mantenimiento

Lo primero no es borrar nada, es congelar el estado actual aunque esté infectado. Esa copia (backup) sirve para volver atrás si rompes algo, comparar después qué había, y tener pruebas si esto acaba en una reclamación al hosting o en una notificación por brecha de datos.

  • Archivos: en el administrador de archivos del hosting, comprime la carpeta public_html (o httpdocs en Plesk) entera y descárgala.
  • Base de datos: phpMyAdmin, selecciona la base de datos, pestaña Exportar, método rápido, formato SQL, y descarga el archivo.
  • Registros: descarga los logs de acceso y de error ahora. El hosting los rota y en pocos días desaparecen. Suelen estar en /home/usuario/logs/, en ~/access-logs/ o en la sección de métricas del panel. Sin ellos no vas a poder saber por dónde entraron.

Guarda esas tres cosas fuera del servidor, en una carpeta con la fecha en el nombre, y no las restaures nunca sobre la web: son material de consulta, no una copia de trabajo.

Después, pon la web en mantenimiento. Mientras siga sirviendo malware a las visitas, Google no te va a quitar el aviso. Lo más limpio es renombrar el index.php y subir un index.html con un aviso, o restringir el acceso por IP en el .htaccess mientras trabajas.

Si tienes una copia anterior al ataque, la tentación de restaurarla y cerrar el asunto es enorme. Es legítimo, pero incompleto: restaurar una copia no cierra la puerta por la que entraron, y si la infección es más antigua de lo que crees, estarás restaurando una web ya tocada.

Sustituir núcleo, temas y plugins por copias oficiales

No intentes desinfectar archivo por archivo lo que puedes reemplazar entero. Es más rápido y mucho más seguro.

El núcleo de WordPress

Descarga de es.wordpress.org la misma versión mayor que tienes instalada. Borra por completo wp-admin/ y wp-includes/ y sube las nuevas. En la raíz, sustituye todos los wp-*.php menos wp-config.php. Después mira qué ha quedado suelto: cualquier archivo PHP que no venga en el paquete oficial es sospechoso por definición. Los nombres típicos imitan a los de verdad (wp-conflg.php, wp-cache-old.php) o son cadenas aleatorias de ocho caracteres.

Plugins y temas

Borra la carpeta completa de cada plugin y reinstálalo desde el repositorio oficial o desde el ZIP del autor. No descomprimas encima: si copias archivos nuevos sobre los viejos, el que sobra se queda ahí tan tranquilo. Y aprovecha para dos decisiones incómodas: un plugin retirado del repositorio oficial no se reinstala, se sustituye; y un plugin o tema de pago descargado de una web de «nulled» no se limpia, se borra y se compra. Ahí estaba el problema en un porcentaje incómodo de los casos.

De los temas, deja solo el que usas y su tema hijo. Los inactivos también contienen archivos PHP a los que se puede llamar directamente por URL.

Por último, abre wp-config.php y léelo entero. Debe empezar por <?php sin ningún espacio delante y no debe contener eval(, base64_decode( ni ningún include que apunte a un archivo con nombre raro.

wp-content: dónde se esconde lo que no se puede sustituir

Aquí está tu contenido, así que aquí no se puede borrar y volver a subir. Hay que mirar.

  • wp-content/uploads/ no debe contener archivos PHP. Nunca, bajo ningún concepto. Si tienes SSH: find wp-content/uploads -type f ( -name "*.php" -o -name "*.phtml" -o -name "*.php7" ). Si no tienes SSH, usa el buscador del administrador de archivos del hosting sobre esa carpeta.
  • Un .htaccess dentro de uploads que active la ejecución de PHP es muy mala señal. Ahí solo debería haber, como mucho, uno que la desactive.
  • Carpetas con años que no existen dentro de uploads (un 2029/) o nombres genéricos tipo uploads/tmp/.
  • wp-content/mu-plugins/. Lo que se pone ahí se ejecuta siempre y no aparece en la lista de plugins del panel. Si existe y tú no la creaste, revisa cada archivo.
  • Carpetas de caché y de copias (wp-content/cache/, upgrade/, backups/). Se pueden vaciar sin miedo y son un escondite habitual.

Es la parte que más paciencia requiere y donde más gente se deja algo dentro. Si quieres el detalle de qué patrones buscar dentro de los archivos, tienes el método completo en la guía sobre cómo buscar puertas traseras en tu web.

Limpiar la base de datos sin perder contenido

Antes de tocar nada aquí, exporta otra vez la base de datos. Y ten en cuenta que el prefijo de las tablas puede no ser wp_: míralo en wp-config.php, línea $table_prefix, y ajusta las consultas.

Primero, en wp_options, comprueba que siteurl y home apuntan a tu dominio y nada más. Después busca opciones enormes que se cargan en cada visita:

SELECT option_name, LENGTH(option_value) AS tam
FROM wp_options
WHERE autoload = 'yes'
ORDER BY tam DESC
LIMIT 20;

Lo normal es ver ahí opciones de tus plugins. Una opción con nombre aleatorio y cientos de kilobytes de contenido ilegible, no. Después, busca inyecciones en el contenido publicado:

SELECT ID, post_title, post_status
FROM wp_posts
WHERE post_content LIKE '%<script%'
   OR post_content LIKE '%eval(%'
   OR post_content LIKE '%base64_decode%';

Ojo: si usas bloques de código, formularios incrustados o vídeos, vas a tener resultados legítimos. Revisa uno a uno y edita a mano. Un buscar y reemplazar masivo a ciegas sobre wp_posts es la forma más eficiente de convertir una web infectada en una web infectada y además rota. Mira también si hay tablas que no pertenecen a nada de lo que tienes instalado.

Usuarios, claves de seguridad y sesiones abiertas

En Usuarios > Todos, filtra por administradores y comprueba que los conoces a todos. Pero no te fíes solo del panel: hay inyecciones que ocultan un usuario del listado. Compáralo con la realidad:

SELECT ID, user_login, user_email, user_registered
FROM wp_users
ORDER BY user_registered DESC;

Si esa consulta devuelve más cuentas de las que ves en el panel, tienes un problema activo, no un resto. Borra las que no reconozcas desde phpMyAdmin y limpia sus filas en wp_usermeta.

Después cambia contraseñas en este orden: administradores de WordPress, cuentas FTP/SFTP (elimina las que no uses en vez de cambiarlas), panel del hosting, usuario de la base de datos (actualiza wp-config.php a la vez) y el correo asociado al administrador.

Y ahora la medida que más rinde y que casi nadie aplica: regenera las claves de seguridad. Entra en https://api.wordpress.org/secret-key/1.1/salt/ y sustituye con lo que te devuelve las ocho constantes AUTH_KEY, SECURE_AUTH_KEY, LOGGED_IN_KEY, NONCE_KEY y sus SALT en wp-config.php. Eso invalida de golpe todas las sesiones abiertas, incluida la del atacante que tuviera una cookie válida. Tendrás que volver a entrar tú también: es lo esperado.

Tareas programadas, cron del panel y archivos ocultos

Esta sección explica por qué una web «limpia» amanece infectada otra vez. Empieza por el cron interno de WordPress:

SELECT option_value FROM wp_options WHERE option_name = 'cron';

Es un texto largo y feo, pero se lee: contiene nombres de eventos. Si ves uno que no corresponde a ningún plugin instalado, busca ese nombre en los archivos de la web.

Mira también el cron del servidor, en cPanel > Trabajos cron. Una tarea que ejecuta wget o curl contra un dominio externo cada pocas horas no la has puesto tú.

Y revisa los .htaccess, todos, no solo el de la raíz: hay uno posible en cada carpeta. Buscas tres cosas:

  • Bloques RewriteCond que miran el HTTP_USER_AGENT o el HTTP_REFERER para tratar distinto a Google o a quien llega desde un buscador. Eso es la firma de una redirección selectiva.
  • Directivas php_value auto_prepend_file, que cargan un archivo antes que cualquier otra cosa.
  • Líneas AddType o AddHandler que hacen que se ejecuten como PHP archivos con otra extensión (.jpg, .ico, .txt).

Comprueba lo mismo en .user.ini y en php.ini si existen en tu carpeta. Y si tienes SSH, este listado te da una foto muy útil de qué se ha tocado últimamente:

find . -type f -name "*.php" -mtime -20 -printf "%TY-%Tm-%Td %pn" | sort

Con honestidad: la fecha de modificación se puede falsear, y un atacante con oficio lo hace. Que un archivo tenga fecha antigua no lo limpia de sospecha; que la tenga reciente sí lo pone el primero de la cola.

Verificar el resultado desde fuera y desde dentro

Desde dentro, lo que quieres es comparar: que cada archivo del núcleo coincida con el oficial y que no quede ninguno de más. Los plugins de seguridad ayudan en esta fase, pero detectan lo que ya está catalogado y poco más; lo tienes explicado en el artículo sobre si los plugins de seguridad limpian de verdad una web hackeada.

Desde fuera es donde se ve la verdad, porque muchas infecciones solo se activan para ciertas visitas:

  • Cómo se ve tu web para el robot de Google: curl -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" -sL https://tudominio.com | head -60
  • Qué tiene Google indexado de ti: busca site:tudominio.com y mira si aparecen URLs, idiomas o productos que no son tuyos.
  • Cómo se comporta desde un móvil con datos, sin tu wifi y sin haber entrado nunca. Muchas redirecciones solo se disparan en móvil y solo la primera vez.

Cuando eso esté limpio, y solo entonces, entra en Search Console, mira Seguridad y acciones manuales > Problemas de seguridad y solicita la revisión. Pedirla antes de tiempo es perder días. Y después vigila quince días: la reinfección, cuando pasa, casi siempre pasa pronto.

Cuándo parar y llamar a alguien

No hace falta que pagues a nadie si tienes un blog o una web corporativa sencilla, sabes más o menos cuándo empezó el problema, tienes una copia de seguridad anterior a esa fecha y el ataque era de los ruidosos (una portada cambiada, un spam evidente). Restaurar, actualizarlo todo, regenerar claves y cambiar contraseñas es una tarde de trabajo y sale bien.

Para y busca ayuda profesional si se da alguna de estas situaciones:

  • Es una tienda con pedidos y datos de clientes. Ahí ya no hablamos solo de limpiar: hay que determinar si hubo acceso a datos personales, y eso tiene plazos legales.
  • Ya has limpiado una vez y ha vuelto. Significa que la puerta trasera sigue puesta o que el vector de entrada sigue abierto.
  • En el mismo hosting tienes varias webs. Se reinfectan entre ellas y limpiarlas de una en una no sirve de nada.
  • Ves indicios de que el atacante no está solo dentro de WordPress sino dentro del servidor: claves nuevas en ~/.ssh/authorized_keys, usuarios del sistema que no creaste, procesos raros.
  • No tienes acceso a la base de datos ni a los archivos, solo al panel de WordPress. Con eso no se puede limpiar bien, y no pasa nada por reconocerlo.

El coste orientativo de una limpieza profesional en España está en el entorno de los 500 euros para una web estándar, y lo que compras no es solo la desinfección: es que alguien determine por dónde entraron y lo cierre.

Cuando termines, no te quedes con la sensación de «creo que ya está». Pasa PathScan por tu dominio y comprueba gratis que no ha quedado nada: analiza tu web desde fuera, como la ve un visitante o un buscador, sin registro y sin tocar tu servidor. Si algo sigue activo, mejor lo ves tú hoy que un cliente mañana.