InfraFenceInfraFence

Live Security Operations

Empezar

Cómo bloquear los ataques de fuerza bruta a xmlrpc.php en WordPress

Última actualización:

xmlrpc.php es la antigua API remota de WordPress. Pocos sitios la siguen necesitando, y todos los sitios WordPress de internet reciben ataques a través de ella. Su método system.multicall ejecuta muchas llamadas en una sola petición, de modo que un solo POST puede probar cientos de contraseñas; por eso los limitadores de inicio de sesión que cuentan las peticiones a wp-login.php no lo ven. Su método pingback.ping se usa para que tu sitio envíe peticiones al de otra persona.

A continuación: cómo reconocer el ataque en los logs de acceso, cómo bloquear xmlrpc.php en nginx, en Apache o en el propio WordPress, qué hacer si usas Jetpack, y cómo limitar la frecuencia o banear con fail2ban cuando tienes que mantenerlo abierto. Los comandos son para Debian y Ubuntu y para la familia RHEL; example.com representa tu sitio.

Reconocer un ataque XML-RPC en los logs de acceso

Cuenta las peticiones POST a xmlrpc.php por dirección. Un sitio normal recibe unas pocas al día, de Jetpack o de una app. Cientos desde una misma dirección, o un flujo constante desde muchas, es un ataque.

Cualquier distribución
sudo sh -c "grep -hI 'POST /xmlrpc.php' /var/log/nginx/*access*.log /var/log/apache2/*access*.log \
  /var/log/httpd/*access_log /usr/local/apache/domlogs/* /var/log/apache2/domlogs/* 2>/dev/null" \
  | awk '{print $1}' | sort | uniq -c | sort -rn | head -20

El comando lee los sitios habituales: nginx, Apache en Debian y Ubuntu, httpd en la familia RHEL y los logs por dominio de cPanel. Las peticiones por hora muestran cuándo empezó:

Cualquier distribución
sudo sh -c "grep -hI 'POST /xmlrpc.php' /var/log/nginx/access.log /var/log/apache2/access.log /var/log/httpd/access_log 2>/dev/null" \
  | awk '{print substr($4, 2, 14)}' | uniq -c | tail -n 24
  • El código de estado es 200 incluso con una contraseña incorrecta: XML-RPC informa del fallo dentro de la respuesta. No interpretes 200 como un inicio de sesión correcto.
  • Las respuestas grandes, repetidas desde una misma dirección cada pocos segundos, suelen ser system.multicall: una respuesta para muchos intentos de contraseña.
  • El log no muestra qué método se llamó: el cuerpo de la petición no se registra. Aquí la fuerza bruta y el abuso de pingbacks se ven igual.

Para saber si XML-RPC responde en un sitio, pídele su lista de métodos desde cualquier máquina. Una lista de métodos significa que está activo; un 403, que está bloqueado.

Cualquier máquina con curl
curl -s -X POST -H 'Content-Type: text/xml' \
  -d '<?xml version="1.0"?><methodCall><methodName>system.listMethods</methodName></methodCall>' \
  https://example.com/xmlrpc.php | head -c 400; echo

Bloquear xmlrpc.php en nginx o Apache

Bloquear en el servidor web es la opción más barata: la petición se rechaza antes de que carguen PHP y WordPress. En nginx, añade esto al bloque server de cada sitio WordPress:

nginx
# In the site's server { } block, before the "location ~ \.php$" block
location = /xmlrpc.php {
    deny all;
}
Cualquier distribución
sudo nginx -t && sudo systemctl reload nginx

En Apache 2.4, ponlo en el .htaccess del sitio (no hace falta recargar; LiteSpeed también lo lee) o en su <VirtualHost>:

Apache
# In the site's .htaccess or <VirtualHost>
<Files "xmlrpc.php">
    Require all denied
</Files>
Debian / Ubuntu
sudo apachectl configtest && sudo systemctl reload apache2
RHEL, Rocky Linux, AlmaLinux, Oracle Linux, Fedora
sudo apachectl configtest && sudo systemctl reload httpd

Atención: Bloquear xmlrpc.php rompe lo que todavía lo usa: Jetpack (ver más abajo), los pingbacks y algunas apps y herramientas de publicación antiguas. Si no estás seguro, revisa antes en los logs del paso 1 si hay llamadas legítimas.

Desactivar XML-RPC dentro de WordPress

Donde no puedes cambiar la configuración del servidor web, un plugin de uso obligatorio (mu-plugin) lo hace desde WordPress. Los mu-plugins se cargan en cada petición y no se pueden desactivar por error desde el panel de administración. Sustituye la ruta por la de tu sitio:

Cualquier distribución
sudo mkdir -p /var/www/example.com/wp-content/mu-plugins
sudo tee /var/www/example.com/wp-content/mu-plugins/disable-xmlrpc.php > /dev/null <<'EOF'
<?php
// Turns off the XML-RPC methods that need a login, and pingbacks.
add_filter( 'xmlrpc_enabled', '__return_false' );
add_filter( 'xmlrpc_methods', function ( $methods ) {
    unset( $methods['pingback.ping'], $methods['pingback.extensions.getPingbacks'] );
    return $methods;
} );
EOF

Nota: El filtro xmlrpc_enabled solo desactiva los métodos que requieren inicio de sesión, como indica la referencia de WordPress; el segundo filtro elimina los métodos de pingback. PHP sigue ejecutándose en cada petición, así que ante un ataque fuerte el bloqueo en el servidor web es más ligero.

Los plugins hacen lo mismo: plugins dedicados del tipo "Disable XML-RPC", y plugins de seguridad como Wordfence y Solid Security tienen una opción para ello (a noviembre de 2026). Un plugin se desactiva fácilmente por accidente durante una actualización; el mu-plugin y la regla del servidor web no.

Mantener Jetpack funcionando

Jetpack se comunica con tu sitio a través de xmlrpc.php, así que bloquearlo para todos rompe la conexión de Jetpack. Jetpack publica los rangos de direcciones que usan sus servidores en su página de lista de permitidos: permítelos y bloquea al resto. En nginx, la location debe además pasar la petición a PHP:

nginx
location = /xmlrpc.php {
    # Jetpack's ranges, from its allowlist page (check it for changes)
    allow 122.248.245.244/32;
    allow 54.217.201.243/32;
    allow 54.232.116.4/32;
    allow 192.0.64.0/18;
    allow 195.234.108.0/22;
    deny all;
    # then the same lines as in your "location ~ \.php$" block, for example:
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass unix:/run/php/php-fpm.sock;
}

Copia las líneas de PHP del propio bloque location ~ \.php$ del sitio: la ruta del socket cambia de un sistema a otro. En Apache, Require ip por sí solo permite únicamente esos rangos:

Apache
<Files "xmlrpc.php">
    Require ip 122.248.245.244/32 54.217.201.243/32 54.232.116.4/32 192.0.64.0/18 195.234.108.0/22
</Files>

Nota: Rangos según la lista de Jetpack en noviembre de 2026. Pueden cambiar: revisa la página de vez en cuando. Detrás de Cloudflare u otro proxy, estas reglas ven la dirección del proxy salvo que antes se restaure la dirección real del visitante (real_ip en nginx, mod_remoteip en Apache).

Limitar la frecuencia de xmlrpc.php cuando debe seguir abierto

Si una app o un servicio que no puedes identificar por dirección necesita XML-RPC, limita la frecuencia con la que cada dirección puede llamarlo. En nginx, define una zona en el contexto http y úsala para xmlrpc.php: aquí 6 peticiones por minuto y dirección, con una ráfaga de 5, y 429 por encima de eso.

nginx
# /etc/nginx/conf.d/xmlrpc-limit.conf (read inside the http { } block)
limit_req_zone $binary_remote_addr zone=xmlrpc:10m rate=6r/m;
nginx
# In the site's server { } block
location = /xmlrpc.php {
    limit_req zone=xmlrpc burst=5 nodelay;
    limit_req_status 429;
    # then the same lines as in your "location ~ \.php$" block, for example:
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass unix:/run/php/php-fpm.sock;
}
Cualquier distribución
sudo nginx -t && sudo systemctl reload nginx

Nota: Un límite de frecuencia cuenta peticiones, no contraseñas: una sola petición system.multicall dentro del límite puede seguir llevando cientos de intentos. Combínalo con contraseñas robustas, o banea las direcciones con fail2ban. Apache no tiene un equivalente integrado; usa fail2ban, a continuación.

Banear a los atacantes de XML-RPC con fail2ban

fail2ban lee el log de acceso y banea una dirección en el firewall, así que funciona igual con nginx que con Apache. Instálalo como en Cómo frenar los ataques de fuerza bruta SSH (repositorios de Debian y Ubuntu, EPEL en la familia RHEL). Después crea un filtro para las peticiones POST a xmlrpc.php:

Cualquier distribución
sudo tee /etc/fail2ban/filter.d/wordpress-xmlrpc.conf > /dev/null <<'EOF'
[Definition]
failregex = ^<HOST> \S+ \S+ \[[^]]+\] "POST /xmlrpc\.php
ignoreregex =
EOF

Y una jail: 5 peticiones en 60 segundos, un baneo de una hora. Pon tu propia dirección en ignoreip y, si usas Jetpack, sus rangos. Para Apache, pon en logpath /var/log/apache2/*access.log en Debian y Ubuntu o /var/log/httpd/*access_log en la familia RHEL.

Cualquier distribución
sudo tee /etc/fail2ban/jail.d/wordpress-xmlrpc.local > /dev/null <<'EOF'
[wordpress-xmlrpc]
enabled  = true
port     = http,https
filter   = wordpress-xmlrpc
logpath  = /var/log/nginx/access.log
maxretry = 5
findtime = 60
bantime  = 3600
ignoreip = 127.0.0.1/8 ::1 192.0.2.10
EOF

Prueba el filtro con el log real antes de activarlo: fail2ban-regex muestra cuántas líneas coinciden. Después reinicia fail2ban y comprueba la jail.

Cualquier distribución
sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/wordpress-xmlrpc.conf
sudo systemctl restart fail2ban
sudo fail2ban-client status wordpress-xmlrpc

Atención: Detrás de Cloudflare u otro proxy, el log de acceso muestra la dirección del proxy: fail2ban banearía el proxy y dejaría el sitio fuera de servicio para muchos visitantes. Restaura antes la dirección real en el log, o bloquea en el proxy.

Cómo gestiona InfraFence los ataques XML-RPC

El firewall web de InfraFence lee los logs de acceso de nginx, Apache y LiteSpeed, y desde el agente 1.0.42 también los de Traefik, Caddy, HAProxy y los servidores web en contenedores. Detrás de Cloudflare toma la dirección del visitante de CF-Connecting-IP, y solo desde los rangos de Cloudflare.

  • Cada 5 peticiones POST a xmlrpc.php desde una dirección a un mismo sitio en un minuto suman 25 puntos a la puntuación de esa dirección. Un ataque sostenido los sigue sumando.
  • La puntuación también recoge los demás ataques de la dirección, como la fuerza bruta en wp-login.php, el escaneo de plugins o los intentos de exploit, y baja 5 puntos por minuto, así que una petición extraña aislada se desvanece.
  • A los 80 puntos la dirección se banea en el firewall y el evento se notifica como crítico. Los umbrales y los puntos se pueden cambiar por servidor, y el modo monitor notifica sin banear.

Consulta Firewall web para las reglas y la puntuación. Lo que no hace: cuenta peticiones, así que no puede ver cuántas contraseñas lleva una petición system.multicall; no cambia tu configuración de nginx, Apache o WordPress, así que xmlrpc.php sigue abierto hasta que lo bloquees como se explica arriba; y no tiene una excepción integrada para Jetpack: si los servidores de Jetpack llaman con la frecuencia suficiente para sumar puntos, añade sus rangos a la lista blanca. El baneo sale del log, así que la petición que lo provocó ya ha recibido respuesta.

Preguntas

¿Es seguro desactivar xmlrpc.php?
Para la mayoría de los sitios, sí: el editor de bloques y el panel de administración usan la API REST, no XML-RPC. Comprueba antes si usas Jetpack, pingbacks, una app o una herramienta de publicación que todavía dependa de él.
¿Por qué los ataques aparecen con estado 200?
XML-RPC responde 200 e informa del inicio de sesión fallido dentro de la respuesta. El código de estado no dice nada sobre si una contraseña funcionó.
Mi plugin limitador de inicios de sesión no lo detiene. ¿Por qué?
Muchos limitadores cuentan los intentos en wp-login.php. Una petición system.multicall a través de xmlrpc.php lleva cientos de intentos en una sola petición y los esquiva. Bloquea o limita el propio xmlrpc.php.

Pruébalo gratis en 1 servidor

Starter es gratuito para un servidor, con protección completa y bloqueo automático. Sin tarjeta de crédito.

Instala el agente como root, con tu token en lugar de <TOKEN>:

curl -fsSL https://github.com/infrafence/infrafence-agent/releases/latest/download/install.sh | sudo bash -s -- --token <TOKEN>

El token se crea en el panel, en Servidores › Añadir servidor.