Un día tu web funciona con normalidad. Al día siguiente empieza a dar errores aleatorios, aparecen páginas que no has creado o recibes alertas del antivirus de tus lectores. En el peor de los casos no notas nada: el atacante lleva semanas dentro y ha instalado una webshell, un archivo PHP oculto que le permite ejecutar comandos en tu servidor como si tuviera acceso directo.
Este artículo documenta paso a paso cómo detectar, analizar y neutralizar este tipo de ataque, basado en un caso real de un sitio WordPress con varios plugins activos. Si gestionas una web para tu negocio en Barbastro, Huesca o cualquier otro punto de Aragón, este es el mismo proceso que sigo cuando un cliente me llega con un WordPress comprometido.
Si sospechas que tu web está comprometida en este momento, no la apagues ni restaures todavía. Primero recopila evidencias: logs de acceso, lista de archivos modificados y volcado de la base de datos. Una restauración precipitada borra las pruebas y no garantiza que el vector de entrada esté cerrado.
Qué es una webshell y cómo llega
Una webshell es un archivo PHP (o a veces JSP, ASPX) que el atacante sube al servidor y que le permite ejecutar comandos del sistema operativo a través del navegador. Desde ahí puede leer archivos de configuración, crear usuarios, enviar spam, instalar malware o usar tu servidor como trampolín para atacar otros sitios.
Vectores de entrada más habituales
- Plugins o temas con vulnerabilidades de subida de archivos — el atacante explota un fallo para subir un PHP disfrazado de imagen o documento.
- Credenciales robadas o por fuerza bruta — acceden al panel de administración y suben el archivo desde el propio gestor de medios o editor de temas.
- Formularios de contacto mal configurados — algunos permiten subir archivos sin validar la extensión real.
- Vulnerabilidades en la función de actualización de plugins — paquetes ZIP que contienen código malicioso junto al plugin legítimo.
- Servidores compartidos comprometidos — si otro dominio del mismo servidor está infectado, puede saltar lateralmente a tu instalación.
Indicadores de compromiso (IoC)
Antes de buscar archivos, revisa estas señales de alerta en tu servidor:
| Indicador | Qué significa | Dónde buscarlo |
|---|---|---|
| /wp-content/uploads/*.php | PHP en la carpeta de subidas — WordPress nunca pone PHPs aquí | FTP / SSH / panel de archivos |
| eval(base64_decode(...)) | Código ofuscado en base64, firma clásica de malware | grep en el servidor |
| Usuarios admin desconocidos | El atacante se crea una cuenta de administrador para persistir | WordPress → Usuarios o BD |
| Peticiones POST a archivos de imagen | Una imagen no debería recibir POST — es la webshell respondiendo | access_log del servidor |
| IPs extranjeras con muchas 200 a /uploads/ | El atacante usando su webshell repetidamente | access_log |
| PHPs con fecha de modificación reciente en lugares raros | Modificación post-instalación sospechosa | find con -newer |
| Opciones de WordPress modificadas (siteurl, admin_email) | Redirección o toma de control del panel | wp_options en la BD |
Proceso de detección paso a paso
-
Buscar PHPs en carpetas que no deberían tenerlos
La carpeta
wp-content/uploads/solo debería contener imágenes, PDFs y documentos. Cualquier.phpahí es malicioso por definición.Buscar PHPs sospechosos# Buscar PHPs en uploads find /var/www/html/wp-content/uploads/ -name "*.php" -type f # Buscar también extensiones disfrazadas find /var/www/html/wp-content/uploads/ \ -name "*.php*" -o -name "*.phtml" -o -name "*.php5" # PHPs modificados en los últimos 30 días en todo el sitio find /var/www/html/ -name "*.php" -newer /var/www/html/wp-config.php \ -not -path "*/node_modules/*" | sort -
Buscar código ofuscado o funciones peligrosas
Las webshells casi siempre usan
eval,base64_decode,systemopassthrupara ejecutar comandos. Un grep rápido delata la mayoría:Grep de patrones de webshell# Código ofuscado en base64 grep -r "eval(base64_decode" /var/www/html/ --include="*.php" -l # Funciones de ejecución de comandos en uploads grep -rE "(system|exec|passthru|shell_exec|popen)\s*\(" \ /var/www/html/wp-content/uploads/ --include="*.php" # Patrón típico de webshell con variable mezclada grep -r '$_POST\[.*\].*eval' /var/www/html/ --include="*.php" -l # Buscar base64 sospechoso (cadenas largas > 500 chars) grep -rP "[A-Za-z0-9+/]{500,}" /var/www/html/wp-content/ \ --include="*.php" -l 2>/dev/null -
Revisar los logs de acceso
El access_log del servidor es la fuente más fiable. Busca peticiones a rutas que no son URLs normales de WordPress, especialmente con códigos HTTP 200 (éxito):
Análisis del access_log# Peticiones POST exitosas a /uploads/ (sospechoso) grep "POST" /var/log/apache2/access.log | grep "/uploads/" | grep " 200 " # IPs con más de 50 peticiones a PHP en uploads grep "/uploads/.*\.php" /var/log/apache2/access.log | \ awk '{print $1}' | sort | uniq -c | sort -rn | head -20 # Accesos a archivos PHP desconocidos con 200 grep " 200 " /var/log/apache2/access.log | \ grep "\.php" | grep -v "wp-" | grep -v "index" | \ awk '{print $7}' | sort | uniq -c | sort -rn | head -20 -
Revisar usuarios administradores en la base de datos
Los atacantes suelen crearse un usuario administrador para mantener acceso incluso si se borra la webshell. Comprueba directamente en la BD:
Usuarios con rol administrator-- En MySQL/MariaDB SELECT u.ID, u.user_login, u.user_email, u.user_registered, um.meta_value as rol FROM wp_users u JOIN wp_usermeta um ON u.ID = um.user_id WHERE um.meta_key = 'wp_capabilities' AND um.meta_value LIKE '%administrator%' ORDER BY u.user_registered DESC;Cualquier administrador que no reconozcas, especialmente con fecha de registro reciente o email en dominio desconocido, es un backdoor.
-
Comprobar opciones críticas de WordPress
Algunos ataques modifican la URL del sitio o el email de administrador para redirigir tráfico o tomar control del panel de recuperación de contraseña:
Opciones sensibles en wp_optionsSELECT option_name, option_value FROM wp_options WHERE option_name IN ( 'siteurl', 'home', 'admin_email', 'blogname', 'users_can_register', 'default_role' );
Neutralización: limpieza paso a paso
Haz un volcado completo de la BD (mysqldump) y una copia del directorio web. Si el proveedor tiene snapshots, toma uno. Trabajarás sobre el sistema en vivo y necesitas poder revertir si algo sale mal.
Bloquear el acceso al atacante
Lo primero es cortar el acceso activo. Si tienes las IPs del atacante en los logs, bloquéalas en el firewall antes de tocar nada:
Si usas Cloudflare, bloquea las IPs desde el panel de Firewall Rules. Es más rápido y no requiere acceso SSH.
Eliminar los archivos maliciosos
Eliminar usuarios administradores creados por el atacante
Cambiar todas las credenciales
- Contraseña de todos los administradores de WordPress — desde el panel o directamente en la BD con
MD5(). - Contraseña de la BD — en el hosting y en
wp-config.php. - Claves secretas de WordPress — regenerarlas en api.wordpress.org/secret-key e insertarlas en
wp-config.php. Esto invalida todas las sesiones activas. - Contraseña FTP/SFTP — si el atacante entró por aquí, tiene las credenciales.
- Contraseña del panel de hosting — por precaución.
Regenerar las claves de seguridad en wp-config.php
Verificar la integridad de WordPress
Compara los archivos del core con la versión oficial para detectar modificaciones:
Desactivar temporalmente el registro de usuarios
Hasta que confirmes que el vector de entrada está cerrado, desactiva el registro para evitar que el atacante cree nuevas cuentas:
Después de limpiar, monitoriza el access_log durante 24-48 horas. Si el atacante sigue intentando acceder a las rutas donde estaban sus webshells y recibe 404, el vector está cerrado. Si recibe 200, hay otro archivo que no encontraste.
Medidas preventivas para que no vuelva a ocurrir
Proteger la carpeta uploads contra ejecución de PHP
Esta es la medida más efectiva. Aunque el atacante suba un PHP, el servidor no lo ejecutará:
Limitar intentos de login
Instala un plugin como Limit Login Attempts Reloaded o configura fail2ban en el servidor para bloquear IPs tras varios intentos fallidos de acceso al panel. Si gestionas también el servidor, esto encaja con el mismo tipo de bastionado que explico en la guía de hardening de sistemas Windows y Linux.
Mantener todo actualizado
La mayoría de las webshells entran por vulnerabilidades conocidas en plugins desactualizados. Activa las actualizaciones automáticas para el core y revisa regularmente los plugins:
Activar autenticación de dos factores
Para todos los usuarios con rol de administrador o editor. Plugins como WP 2FA o Google Authenticator añaden una capa adicional que hace inútiles las contraseñas robadas.
Usar contraseñas únicas y largas
Una contraseña de 20 caracteres aleatorios hace los ataques de fuerza bruta computacionalmente inviables. Usa un gestor de contraseñas (Bitwarden, 1Password) para generarlas y almacenarlas.
Monitorizar cambios en los archivos
Configura un sistema que te alerte cuando se modifiquen archivos PHP fuera de las actualizaciones planificadas. Wordfence tiene esta función, o puedes usar inotifywait en el servidor:
Configurar permisos correctos
Limpiar usuarios subscriber spam periódicamente
Los bots registran cuentas de subscriber automáticamente. No tienen permisos peligrosos, pero acumulan basura en la BD y pueden usarse en ataques posteriores:
Resumen del flujo completo
| Fase | Acción principal | Herramienta |
|---|---|---|
| Detección | Buscar PHPs en uploads y código ofuscado | find, grep, SSH |
| Análisis | Revisar logs de acceso, IPs, usuarios BD | access_log, MySQL |
| Contención | Bloquear IPs atacantes, desactivar registro | iptables, wp_options |
| Limpieza | Borrar webshells, usuarios y opciones comprometidas | rm, MySQL, WP-CLI |
| Recuperación | Cambiar todas las contraseñas y claves secretas | wp-config.php |
| Prevención | Bloquear ejecución en uploads, 2FA, actualizaciones | .htaccess, plugins |
El paso más importante no es la limpieza sino encontrar y cerrar el vector de entrada. Si el atacante entró por un plugin vulnerable y ese plugin sigue instalado y sin actualizar, volverá a entrar en horas. La limpieza sin cerrar la puerta es trabajo perdido.
Checklist prioritario si tu WordPress está hackeado
Si tienes que empezar por algún sitio, este es el orden que más impacto tiene en el menor tiempo posible:
- Recopilar evidencias (logs, archivos modificados, volcado de BD) antes de tocar nada
- Buscar PHPs en
wp-content/uploads/y código ofuscado coneval(base64_decode - Revisar usuarios administradores desconocidos en la base de datos
- Bloquear las IPs atacantes identificadas en el access_log
- Eliminar webshells, usuarios maliciosos y opciones de WordPress modificadas
- Cambiar todas las contraseñas y regenerar las claves secretas de
wp-config.php - Bloquear la ejecución de PHP en
uploads/con.htaccess - Actualizar core, plugins y temas, y activar 2FA en todos los administradores
- Monitorizar el access_log 24-48h para confirmar que el vector está cerrado
Preguntas Frecuentes sobre WordPress Hackeado
No apagues la web ni restaures un backup todavía. Primero recopila evidencias: copia los logs de acceso, haz una lista de los archivos PHP modificados recientemente y guarda un volcado de la base de datos. Restaurar antes de investigar borra las pruebas y no garantiza que el vector de entrada esté cerrado, así que el atacante podría volver a entrar por el mismo sitio.
Una webshell es un archivo PHP que el atacante sube al servidor y que le permite ejecutar comandos del sistema desde el navegador. Los vectores más habituales son plugins o temas con vulnerabilidades de subida de archivos, credenciales robadas o por fuerza bruta, formularios de contacto mal configurados y servidores compartidos donde otro dominio ya estaba comprometido.
Depende de la gravedad: cuántos archivos están afectados, si hay usuarios administradores creados por el atacante y si tu web ha entrado en listas negras de Google o del hosting. El diagnóstico inicial es gratuito; a partir de ahí se da un presupuesto cerrado según el alcance real de la limpieza.
Revisa Google Search Console, sección Seguridad y acciones manuales. Si aparece un aviso de contenido pirateado o malware, Google ya ha marcado tu web. También puedes comprobarlo directamente en el Transparency Report de Google Safe Browsing introduciendo tu dominio. Una vez limpio el sitio, se solicita una revisión desde Search Console para salir de la lista.
No, si no cierras antes el vector de entrada. Si el backup es anterior al ataque pero el plugin vulnerable sigue instalado y desactualizado, o las credenciales robadas siguen siendo válidas, el atacante volverá a entrar en horas. Restaurar sin investigar y cerrar la puerta de entrada es limpiar el suelo con la ventana rota abierta.
Las medidas de mayor impacto son: bloquear la ejecución de PHP en wp-content/uploads, mantener el core y los plugins siempre actualizados, activar autenticación de dos factores para todos los administradores, usar contraseñas largas y únicas, y monitorizar cambios en archivos PHP fuera de las actualizaciones planificadas.
¿Tu WordPress está hackeado ahora mismo?
Diagnóstico gratuito y reparación urgente de webs hackeadas o comprometidas en Barbastro, Huesca y el resto de Aragón. Elimino malware y backdoors, cierro el vector de entrada y dejo tu web funcionando y segura.
Solicitar reparación urgente →
💬 5 comentarios
Justo lo que necesitaba. Tenía un WooCommerce con peticiones POST raras a /uploads/ en el access_log y no sabía interpretarlo. El grep de
eval(base64_decodeencontró dos archivos que llevaban ahí semanas. Lo que no sabía es que también debía revisar wp_options por si habían cambiado el admin_email, menos mal que lo leí antes de dar la limpieza por terminada.Raúl, ese es uno de los pasos que más se saltan. El admin_email es crítico porque si lo cambian pueden pedir "olvidé mi contraseña" y recuperar el acceso al panel aunque hayas cambiado todas las contraseñas. Revísalo siempre junto a siteurl y home antes de dar la limpieza por cerrada.
Llevaba tres días con una web en la lista negra de Google Safe Browsing sin saber por qué caía el tráfico a cero. El aviso estaba en Search Console, en Seguridad y acciones manuales, tal como dices. Después de limpiar y pedir la revisión tardó unas 24 horas en quitarse el aviso. Un susto importante para un comercio pequeño.
El .htaccess de uploads para bloquear PHP debería venir activado por defecto en WordPress, punto. Lo apliqué en las cuatro webs que gestiono para clientes y en ninguna rompió nada, porque nunca debería haber PHP ejecutable ahí. Cinco minutos de trabajo por una tranquilidad enorme.
Totalmente de acuerdo, Jorge. Es de las pocas medidas de seguridad con coste cero y beneficio altísimo. La única precaución es si tienes algún plugin que genere PDFs o imágenes dinámicamente dentro de uploads vía PHP —son casos raros, pero conviene comprobarlo antes de aplicar la regla en producción.