Recuperar y migrar un WordPress comprometido

Hace unos días tuve que migrar un WordPress a un servidor nuevo después de que el anterior quedara comprometido. Cuando llegas a un punto en el que faltan binarios del sistema, hay procesos extraños o cron jobs que no reconoces, seguir intentando limpiar la máquina deja de tener sentido. En estos casos prefiero recuperar los datos necesarios y reconstruir el servidor desde cero. Este fue exactamente el proceso que seguí.

1. Recuperar el WordPress del servidor antiguo

Lo primero fue sacar la carpeta completa del WordPress.

En mi caso estaba en:

/var/www/html/wordpress

Desde mi máquina local ejecuté:

scp -r root@<server_ip>:/var/www/html/wordpress .

Con esto copié todo el código del sitio a mi máquina local.

2. Recuperar la base de datos

Para identificar la base de datos revisé el wp-config.php:

grep DB_ wordpress/wp-config.php

Una vez identificado el nombre de la base de datos y el usuario, generé el dump.

mysqldump --no-tablespaces -u wordpress_user -p wordpress_db > /root/wordpress_no_tablespaces.sql

Tuve que usar --no-tablespaces porque el usuario no tenía permisos PROCESS.

Después lo copié a local:

scp root@<server_ip>:/root/wordpress_no_tablespaces.sql .

3. Recuperar configuración del servidor

También guardé algunas configuraciones del servidor antiguo por si necesitaba replicar algo.

nginx

scp -r root@<server_ip>:/etc/nginx ./backup_server/

PHP

scp -r root@<server_ip>:/etc/php ./backup_server/

certificados SSL

scp -r root@<server_ip>:/etc/letsencrypt ./backup_server/

logs

scp -r root@<server_ip>:/var/log/nginx ./backup_server/

Al final terminé con esta estructura:

wordpress/
wordpress_no_tablespaces.sql
backup_server/nginx
backup_server/php
backup_server/letsencrypt
backup_server/logs

4. Probar el WordPress en local con Docker

Antes de restaurar nada en un servidor nuevo preferí comprobar que el backup funcionaba correctamente y que no había nada extraño en el código. Para hacerlo monté un entorno local con Docker.

La idea era reproducir el stack del servidor:

  • nginx

  • php-fpm

  • mysql

Primero construí el entorno:

docker compose up -d --build

Luego importé la base de datos dentro del contenedor:

docker exec -i wp_db mysql -u root -pwordpress_root_password wordpress_db < wordpress_no_tablespaces.sql

Después accedí al WordPress desde el navegador:

http://localhost

Esto me permitió comprobar:

  • que el sitio cargaba correctamente

  • que la base de datos estaba intacta

  • que los plugins funcionaban

  • que no había código sospechoso

Además ejecuté algunas comprobaciones básicas.

Buscar PHP en uploads:

find wordpress/wp-content/uploads -name "*.php"

Buscar funciones sospechosas:

grep -R "base64_decode(" wordpress

Una vez comprobado que todo funcionaba correctamente continué con la migración.

5. Montar un servidor nuevo

Con el backup verificado monté un servidor nuevo con Ubuntu 24.04.

Actualicé el sistema:

apt update
apt upgrade -y

6. Instalar el stack del servidor

Ubuntu 24.04 instala PHP 8.3 por defecto, pero en mi caso quería utilizar PHP 8.4, así que añadí el repositorio de PHP mantenido por Ondřej Surý.

Primero instalé las herramientas necesarias:

apt install -y software-properties-common

Después añadí el repositorio:

add-apt-repository ppa:ondrej/php
apt update

Una vez añadido el repositorio instalé nginx, MySQL y PHP 8.4 con las extensiones necesarias para WordPress:

apt install -y nginx mysql-server php8.4-fpm php8.4-mysql php8.4-cli php8.4-curl php8.4-gd php8.4-mbstring php8.4-xml php8.4-zip php8.4-intl unzip certbot python3-certbot-nginx fail2ban

Después comprobé la versión de PHP instalada:

php -v

La salida debería mostrar algo similar a:

PHP 8.4.x (cli)

También verifiqué que el socket de PHP-FPM estaba disponible:

ls /run/php/

Debería aparecer:

php8.4-fpm.sock

Este es el socket que luego se utiliza en la configuración de nginx.

7. Crear la base de datos

Entré en MySQL:

mysql

Creé la base de datos y el usuario:

CREATE DATABASE wordpress CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wordpress_user'@'localhost' IDENTIFIED BY 'your_secure_password';
GRANT ALL PRIVILEGES ON wordpress.* TO 'wordpress_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;

8. Subir el dump al servidor nuevo

Desde mi máquina local:

scp wordpress_no_tablespaces.sql root@<new_server_ip>:~

Importé la base de datos:

mysql -u wordpress_user -p wordpress < ~/wordpress_no_tablespaces.sql

9. Subir el WordPress

scp -r wordpress/* root@<new_server_ip>:/var/www/<domain>

Permisos correctos:

chown -R www-data:www-data /var/www/<domain>

10. Configurar nginx

Creé el virtual host:

nano /etc/nginx/sites-available/<domain>

Configuración básica:

server {
    listen 80;
    server_name <domain>;
root /var/www/<domain>;
index index.php index.html; location / { try_files $uri $uri/ /index.php?$args; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php8.4-fpm.sock; } }

Activé el sitio:

ln -s /etc/nginx/sites-available/<domain> /etc/nginx/sites-enabled/

11. Activar HTTPS

certbot --nginx -d <domain>

12. Comprobaciones finales

Verificar PHP en uploads:

find /var/www/<domain>/wp-content/uploads -name "*.php"

Comprobar cronjobs:

crontab -l
ls /etc/cron*

Verificar renovación SSL:

certbot renew --dry-run

Conclusión

Cuando un servidor ha sido comprometido realmente, la forma más rápida de volver a tener control sobre el sistema es reconstruir el entorno desde cero.

En mi caso el proceso fue:

  1. recuperar WordPress y base de datos

  2. validar el backup en Docker

  3. montar un servidor limpio

  4. restaurar el sitio

Puede parecer más trabajo que intentar limpiar el servidor original, pero a largo plazo suele ser mucho más seguro.

Comentarios