Suena el teléfono: lo que se sabe y lo que se ignora
El cliente describe un síntoma, rara vez una causa: el sitio redirige a los visitantes, a veces solo en móvil; el navegador muestra «Sitio engañoso»; Google señala contenido pirateado; el alojamiento ha suspendido la cuenta. La documentación oficial de WordPress recuerda que «hackeo» es un término ambiguo: registre primero los indicadores de compromiso (sitio listado por un buscador, cuenta desactivada, usuarios que nadie ha creado). Anote la hora de la llamada, la zona horaria, las palabras del cliente y los últimos cambios conocidos: un plugin nuevo, un tema cambiado, un acceso dado a un proveedor. Es la primera línea del diario de incidente.
Seis reglas que fijar desde la llamada
- Un responsable del incidente — una sola persona decide, escribe en el diario y habla con el cliente; los demás ejecutan.
- Una sola voz hacia el cliente — el cliente no recibe dos versiones de los hechos.
- Un diario fechado — cada acción se anota con la hora UTC, el autor, el comando lanzado y su resultado.
- Nada se borra antes de copiarse — un archivo borrado ya no dice por dónde entró el atacante.
- Ninguna corrección en caliente — se aísla, se analiza y después se decide.
- Ninguna promesa sobre la causa o la duración — «reparado» solo se dice tras los controles de reapertura.
Aislar sin destruir las pruebas
Abrir el diario de incidente (5 min)
date -u +%Y-%m-%dT%H:%M:%SZ
Anote esta hora, el síntoma comunicado, el nombre del sitio, el alojamiento y las personas que intervienen. Cada línea siguiente indica la hora UTC, la acción y su resultado. El archivo se entregará al cliente, así que se mantiene factual.Cortar el acceso público (10 min)
Sirva una página de mantenimiento con el código HTTP 503 desde el servidor web o la CDN, o limite el acceso a la IP del equipo. No renombre aún carpetas de plugins o temas: cambiaría las fechas de modificación que querrá leer. Si el sitio difunde malware, el corte va antes de la copia.
Copiar archivos y base de datos (15 min)
mkdir -p ~/incidents/client-a/AAAA-MM-DDrsync -a /var/www/client-a/ ~/incidents/client-a/AAAA-MM-DD/archivos/wp db export ~/incidents/client-a/AAAA-MM-DD/base.sql --skip-plugins --skip-themesfind ~/incidents/client-a/AAAA-MM-DD -type f -exec sha256sum {} + > ~/incidents/client-a/hashes-AAAA-MM-DD.txtchmod -R a-w ~/incidents/client-a/AAAA-MM-DD
Las opciones--skip-plugins --skip-themesevitan cargar plugins y tema, pero el código del sitio sigue siendo sospechoso. Las huellas y la retirada de permisos de escritura permiten demostrar después que la copia no ha cambiado.Copiar los registros (5 min)
Recupere los registros de acceso y de errores del servidor web y los del panel de alojamiento. Su conservación es corta en muchas ofertas: es la prueba que desaparece más rápido y la que permite fechar la intrusión.
Cerrar los accesos no esenciales (10 min)
Cambie las contraseñas del panel, de SFTP y de SSH, retire los accesos de los proveedores y sustituya las claves de sal de
wp-config.phppara invalidar las sesiones. Esta rotación es provisional: una puerta trasera puede devolver el control al atacante, así que se repite tras la limpieza.Avisar al alojamiento (5 min)
En un servidor compartido, la infección puede ir más allá del sitio. Informe al soporte, pida que se conserven los registros y, si la cuenta está suspendida, un acceso de lectura en lugar de la reactivación. Pregunte si la IP del servidor figura en una lista de bloqueo de correo.
Comprobar la integridad, comando a comando
Archivos del núcleo (5 min)
wp core verify-checksums --include-root
Compara los archivos instalados con las sumas de comprobación de WordPress.org. Un mensaje «File doesn't verify against checksum» señala un archivo modificado y «File should not exist» uno añadido;--include-rootextiende el control a la raíz.Plugins (5 min)
wp plugin verify-checksums --all
Mismo principio para los plugins del repositorio de WordPress.org. Un plugin premium o un tema a medida no tienen suma publicada: compárelos con una copia limpia del editor.PHP donde no debe haberlo (10 min)
find wp-content/uploads -name '*.php'find . -type f -name '*.php' -mtime -30
La carpeta de medios no contiene código: cualquier PHP allí es sospechoso. El segundo comando lista los PHP modificados en los últimos treinta días; sustituya 30 por la antigüedad estimada de la intrusión. Una fecha de modificación puede falsificarse: orienta, no prueba.Patrones de código sospechosos (10 min)
grep -rEl 'eval\(|base64_decode\(|gzinflate\(' wp-contenteval,base64_decodeygzinflatesirven para ocultar código, pero plugins legítimos también los usan: lea cada resultado. Una búsqueda sin resultados no prueba la ausencia de compromiso.Cuentas y ajustes (10 min)
wp user list --role=administrator --fields=ID,user_login,user_email,user_registeredwp option get siteurlwp option get homewp option get users_can_registerwp option get default_role
Busque un administrador desconocido, una fecha de creación cercana a la intrusión, unsiteurlo unhomemodificados (causa frecuente de redirecciones), un registro abierto con un rol por defecto demasiado alto. Estos datos están en las tablaswp_users,wp_usermetaywp_options; el prefijo puede diferir.Tareas programadas y archivos de arranque (10 min)
wp cron event listcrontab -lls -la wp-content/mu-pluginsfind . -name '.htaccess'
Una tarea programada desconocida puede recrear el archivo malicioso cada noche. Los plugins de uso obligatorio (mu-plugins) se cargan sin activación. El.htaccessse modifica a menudo: reglas reservadas a móviles o a visitantes procedentes de un buscador explican por qué el cliente no ve nada desde su puesto. Puede haber otros en subcarpetas.Contenido de la base de datos (10 min)
wp db search '<script' --all-tables-with-prefixwp option get active_plugins --format=json
Busque etiquetas de script inyectadas en entradas, widgets y opciones, y relea la lista de plugins activos: una entrada desconocida salta a la vista.
Una búsqueda vacía no prueba nada
El código malicioso suele estar ofuscado, fragmentado o cargarse desde la base de datos. Las sumas de comprobación solo cubren el núcleo y los plugins del repositorio oficial: un tema a medida o un archivo añadido en la carpeta de contenido se les escapan. Trate estos resultados como indicios para la decisión siguiente, nunca como un certificado de limpieza.
Fechar la intrusión antes de elegir
El cliente pregunta «¿limpiamos o restauramos?», y la respuesta depende de una fecha: la de la intrusión. Restaurar elimina el síntoma visible, pero una copia que ya contiene la puerta trasera repite la infección, y restaurar sin corregir el fallo deja volver al atacante por el mismo camino. Limpiar a mano suele dejar un archivo olvidado. Para fechar la intrusión, cruce al menos dos fuentes: el archivo sospechoso más antiguo, la fecha de creación de la cuenta desconocida (user_registered), la primera petición anómala en los registros de acceso, la fecha de instalación de un plugin vulnerable. Conserve la fecha más antigua que pueda demostrar y réstele un margen. La fecha de la última copia sana se compara después con ella.
Limpiar, restaurar o reconstruir: la tabla de decisión
Desplace la tabla
| Situación | Decisión | Motivo |
|---|---|---|
| Última copia sana anterior a la intrusión, poco contenido escrito desde entonces | Restaurar y luego corregir el fallo | La vuelta atrás es corta y verificable con los controles de integridad |
| Copia sana antigua, pero pedidos o contenidos escritos desde entonces (tienda, formularios) | Restaurar o reconstruir y recuperar los datos recientes de la copia preservada | Se recuperan las tablas útiles, no los archivos dudosos; la selección se valida con el cliente |
| Fecha de intrusión desconocida, o la propia copia es sospechosa | Reconstruir: núcleo y plugins reinstalados limpios, contenido y medios importados tras la selección | Ningún punto de partida fiable: solo se reutiliza lo que se puede verificar |
| Un solo archivo modificado, causa identificada, copia limpia del núcleo disponible | Limpiar sustituyendo por archivos oficiales | El perímetro está acotado y las sumas de comprobación lo confirman |
| Varios archivos modificados fuera del núcleo, cuentas desconocidas, plugin de uso obligatorio desconocido | Reconstruir | El número de puntos de entrada posibles supera lo que una revisión manual puede garantizar |
| Plugin vulnerable aún instalado, sin parche publicado | Retirarlo o sustituirlo, sea cual sea la vía elegida | Si no, el atacante vuelve por la misma puerta |
Reabrir: actualización y rotación de secretos, en orden
Actualizar el núcleo y los plugins
wp core updatewp plugin update --all
Hágalo en un sitio limpio, tras la restauración o la limpieza. Retire también los temas y plugins sin uso.Panel de alojamiento, SFTP y SSH
Contraseñas nuevas, largas y únicas, guardadas en una bóveda compartida. Elimine las claves desconocidas de
~/.ssh/authorized_keys. Se empieza por estos accesos: permiten reescribir los archivos, así que el resto no sirve de nada mientras estén comprometidos.Base de datos
Contraseña nueva para el usuario de la base de datos y actualización en
wp-config.php. La documentación oficial sugiere cambiar también el nombre de ese usuario.Claves de sal de wp-config.php
Genere un juego nuevo en https://api.wordpress.org/secret-key/1.1/salt/ y sustituya los valores: las sesiones abiertas se cierran. Repítala ahora: la rotación inicial pudo haber sido eludida.
Cuentas de WordPress
Restablezca las contraseñas de todos los administradores y editores, elimine las cuentas no reconocidas, active la doble autenticación y revise los roles con el comando
wp user list --role=administrator.Claves de API y servicios de terceros
Pagos, SMTP, CDN, almacenamiento de objetos, antispam: regenere cada clave en el proveedor, no solo en el sitio.
Tokens de gestión remota
Herramientas de gestión multisitio, copia en línea, supervisión: revoque los tokens antiguos y vuelva a conectar el sitio con otros nuevos.
Comprobar antes de abrir al público
- Sumas de comprobación limpias —
wp core verify-checksumsywp plugin verify-checksums --allya no devuelven avisos; los plugins ajenos al repositorio se comparan con una copia reciente del editor. - Cuentas inventariadas — la lista de administradores se revisa con el cliente, línea por línea, y cada cuenta se asocia a una persona.
- Ningún PHP en la carpeta de medios —
find wp-content/uploads -name '*.php'no devuelve nada. - Análisis complementario — un escáner de plugin y un escáner remoto, porque la documentación oficial recuerda que ninguna herramienta ve por sí sola todos los casos.
- Vigilancia reforzada durante un periodo fijado — por ejemplo catorce días, con los controles de integridad repetidos a diario y la lectura de los registros de conexión; la fecha de fin se escribe en el diario de incidente.
- Una copia sana fechada — la copia hecha justo tras la reapertura pasa a ser el nuevo punto de referencia para la próxima decisión.
El mensaje al cliente, en tres tiempos y con una sola voz
La constatación, en la hora siguiente
Escribe la agencia, no el alojamiento ni el equipo técnico. Modelo: «Su sitio presenta signos de compromiso: [síntoma observado]. Lo hemos puesto fuera de línea a las [hora UTC] para proteger a sus visitantes. La causa aún no se conoce.» Ninguna causa supuesta, ningún plazo prometido.
La acción, una vez hecho el aislamiento
Modelo: «Se ha conservado una copia completa. Estamos verificando los archivos, las cuentas y la base de datos. Antes de las [hora], le diremos si limpiamos, restauramos o reconstruimos, y por qué.» Si hay pedidos recientes en juego, dígalo: el cliente decide lo que acepta perder.
El seguimiento, en la reapertura
Modelo: «El sitio está reabierto desde el [fecha]. Los accesos se han renovado y lo vigilamos durante [duración]. Le pedimos que cambie la contraseña de sus cuentas y que nos señale cualquier comportamiento inusual.» Escriba «corregido» solo para lo que los controles han verificado de verdad.
El diario de incidente, entregado al cliente
Adjunte el diario con marcas de tiempo: constatación, acciones, decisión y motivo, reapertura, periodo de vigilancia. Se mantiene factual y no atribuye ninguna culpa.
Salir de la advertencia de Google tras la reapertura
Si el navegador mostraba «Sitio engañoso» o los resultados señalaban contenido pirateado, la salida pasa por Search Console. El informe de problemas de seguridad (Security issues) lista las páginas afectadas. La documentación de Google pide solicitar la revisión solo cuando todos los problemas estén corregidos en todas las páginas: una solicitud prematura puede alargar el plazo de la siguiente, o incluso hacer que el sitio se clasifique como reincidente. La solicitud describe el problema, las medidas tomadas y el resultado. El plazo anunciado va de unos días a varias semanas: dé ese margen al cliente en lugar de una fecha. Corrija también el fallo: si no, el sitio volverá a infectarse.
Cuando la salida no dice lo que parece decir
- «File should not exist» en la raíz — el archivo no figura en la lista oficial, así que se añadió. No todos son hostiles (un archivo de verificación de propiedad, por ejemplo), pero cada uno debe explicarse.
- «File doesn't verify against checksum» en
readme.htmlowp-config-sample.php— a menudo inofensivo si el idioma o la versión difieren. Compare--localey--versioncon lo que muestra la pantalla Actualizaciones del escritorio antes de concluir. - Falla la descarga de las sumas de comprobación — quizá el servidor no tenga acceso saliente. No añada
--insecureen una máquina sospechosa: la opción desactiva la validación del certificado y expone a una interceptación. - Plugin ausente del control — no está en el repositorio oficial. Compare sus archivos con una copia limpia del editor en lugar de declararlo sano.
- El sitio sigue redirigiendo tras la limpieza — pruebe como un visitante:
curl -sI -A 'Mozilla/5.0 (iPhone)' https://your-domain.com/y despuéscurl -sI -e https://www.google.com/ https://your-domain.com/. Una redirección visible solo así procede de una regla.htaccess, de una caché del servidor o de la CDN: liste todos los.htaccessy purgue las cachés.
Para ir más lejos
- Un incidente fechado de un plugin de copias — /blog/updraftcentral-cles-volees-backdoors-wordpress-sauvegarde describe una restauración limpia en un caso concreto; esta guía da el procedimiento general.
- Demostrar la restauración antes de necesitarla — /blog/tester-restauration-sauvegarde-vps-client-agence-procedure explica cómo probar una copia fuera de cualquier incidente.
- Revisar el parque cada mes — /blog/checklist-audit-securite-mensuel-vps-clients-agence previene parte de las situaciones descritas aquí.
La documentación oficial
FAQ «My site was hacked» de WordPress: https://wordpress.org/documentation/article/faq-my-site-was-hacked/
Endurecimiento de WordPress: https://wordpress.org/documentation/article/hardening-wordpress/
Comando wp core verify-checksums: https://developer.wordpress.org/cli/commands/core/verify-checksums/
Comando wp plugin verify-checksums: https://developer.wordpress.org/cli/commands/plugin/verify-checksums/
Comando wp db search: https://developer.wordpress.org/cli/commands/db/search/
Informe de problemas de seguridad de Google Search Console: https://support.google.com/webmasters/answer/9044101