XML-RPC de WordPress: 3 formas de desactivarlo y cómo identificar ataques a xmlrpc.php | Excepción de Jetpack

Si el registro de acceso de tu sitio WordPress muestra cientos de solicitudes seguidas a xmlrpc.php desde la misma IP, es un ataque de contraseñas dirigido a XML-RPC de WordPress. Clasifiqué las 8.624 entradas de un día del registro de acceso de un servidor Nginx + Apache con varios sitios WordPress, apliqué yo mismo las tres formas de desactivarlo y comprobé los códigos de respuesta, para aclarar en qué capa hay que bloquearlo para que ni siquiera se ejecute PHP.
XML-RPC es un protocolo que intercambia documentos XML por HTTP para llamar a funciones de WordPress de forma remota; desde WordPress 3.5 desapareció la opción de activarlo o desactivarlo en los ajustes, así que funciona activado desde la instalación.
1️⃣ Qué hace xmlrpc.php y quién lo usa
Desde que existe la REST API es fácil ver [xmlrpc.php] como un archivo que ya nadie usa, pero Jetpack sigue conectando el sitio con WordPress.com a través de él. La documentación oficial de Jetpack indica que depende de la conexión XML-RPC, y en los hostings que bloquean XML-RPC por defecto la propia conexión de Jetpack falla. Es como la oficina que todavía conserva un número de fax.
En WordPress 7.1 este archivo acepta 77 métodos: 50 de la familia wp, que manejan entradas, medios y comentarios; 23 de los antiguos protocolos de blogs (MetaWeblog, MovableType, Blogger); 2 de pingback y 2 demo para pruebas.
La familia wp inicia sesión con el usuario y la contraseña que van en el cuerpo de la solicitud, así que no pasa por la pantalla de acceso. Crear y editar entradas, subir medios y gestionar comentarios pertenecen a esta familia, y los 23 métodos de los antiguos protocolos de blogs se conservan para intercambiar entradas con otras herramientas de blogs de la misma manera.
demo.sayHello es una llamada de prueba incluida en XML-RPC de WordPress. Al enviarla devuelve una sola palabra, [Hello!]; como no necesita iniciar sesión, sirve para comprobar si XML-RPC funciona. Su pareja, demo.addTwoNumbers, suma dos números y devuelve el resultado.
Todos responden solo a un cuerpo XML enviado por POST; si abres la dirección en el navegador, vuelve un 405 con el texto [XML-RPC server accepts POST requests only.].
Los pingbacks también son tarea de este archivo. Al publicar una entrada, se envía un pingback al xmlrpc.php de los sitios enlazados en el texto, y el servidor receptor abre él mismo la dirección de origen para comprobar que el enlace existe de verdad.
2️⃣ Ataques a xmlrpc.php vistos en el registro de acceso
Los intentos de contraseña no pasan por la pantalla de acceso: entran por [xmlrpc.php]. En el registro de acceso de un servidor Nginx + Apache con varios sitios WordPress, entre las 0:00 y las 20:00 del 14 de octubre hubo 8.624 solicitudes POST /xmlrpc.php desde 72 IP, y 19 de esas IP enviaron exactamente 300 cada una.
Una IP envió 300 solicitudes en 28 minutos, de las 14:09 a las 14:37, con un intervalo constante de 5 a 6 segundos hasta el final. 8.524 solicitudes llegaron con Jetpack o WordPress.com en el User-Agent, y la misma IP alternaba varios valores que imitaban el formato real de Jetpack, como [Jetpack by WordPress.com (Jetpack 13.0; WordPress 6.3)].
Hasta las 14:00 de ese día, la opción de desactivar XML-RPC del plugin de seguridad devolvía estas solicitudes a la portada con un 302; después de las 14:00, ya bloqueadas en Nginx, terminaban en 403. El 302 significa que PHP se ejecutó en cada solicitud aunque el cuerpo fuera de 0 bytes; el 403 es una respuesta que no pasó por PHP.
3️⃣ system.multicall y los intentos de contraseña
Muchas explicaciones dicen que con [system.multicall] una sola solicitud prueba cientos de contraseñas, pero en cuanto falla un inicio de sesión dentro de la solicitud, WordPress deja de comprobar las contraseñas de las llamadas restantes. Solo la primera llamada pasa por wp_authenticate; las demás reciben el mismo error, [Incorrect username or password.], sin comprobación.
Como vuelve una respuesta por cada llamada, una sola solicitud con 100 contraseñas incorrectas recibe 100 errores en una respuesta de 23 KB en 0,12 segundos. Si la contraseña correcta va después en la misma solicitud, se rechaza junto con el fallo anterior; si va primero, solo esa llamada tiene éxito. La comparación de contraseña es una sola, pero el análisis del XML y la carga de WordPress se repiten en cada solicitud, así que la carga que este ataque impone al servidor viene del número de solicitudes, no de la comprobación de contraseñas.
4️⃣ 3 formas de desactivar XML-RPC y sus códigos de respuesta
Aunque desactives XML-RPC en un plugin, las solicitudes a [xmlrpc.php] siguen llegando a PHP. Con el filtro activo envié una solicitud con 100 llamadas y aun así volvió un HTTP 200 con 100 errores (24 KB). Según la capa que rechaza la solicitud, los métodos se dividen en tres, y cada uno tiene un código de respuesta distinto.
1. Desactivarlo por código con el filtro xmlrpc_enabled
Basta con añadir al functions.php del tema una línea add_filter que devuelva false para el filtro [xmlrpc_enabled]. En este estado, las llamadas que requieren iniciar sesión se rechazan con el error [XML-RPC services are disabled on this site.] y HTTP 405, pero la llamada de prueba demo.sayHello, que no necesita sesión, sigue respondiendo con 200.
2. Desactivarlo desde los ajustes de un plugin: Perfmatters · Hide My WP
Es la forma de desactivarlo con el ajuste Disable XML-RPC de plugins como Perfmatters o Hide My WP. Hide My WP usa el mismo método de filtro que el anterior, así que sus respuestas son iguales, pero Perfmatters, además del filtro, termina las solicitudes directas a [xmlrpc.php] con un 403 y elimina la cabecera y el enlace de pingback, de modo que, una vez desactivado, tanto GET como POST responden 403. Eso sí, para emitir ese 403 WordPress se ejecuta hasta la fase init, así que la respuesta tarda 0,13–0,15 segundos, lo mismo que cuando está abierto, y la carga de PHP se mantiene.
3. Bloquearlo en el servidor web: Nginx · Apache
Tanto Nginx como Apache rechazan la solicitud antes de PHP, así que, a diferencia de los plugins, WordPress no se ejecuta.
# Nginx — dentro del bloque server, encima del location de PHP
location = /xmlrpc.php {
deny all;
}Este bloque location se coloca encima del location de PHP, y Nginx rechaza de inmediato las solicitudes que coinciden exactamente con [xmlrpc.php]. Después de añadirlo, comprueba la configuración y recárgala para que se aplique.
nginx -tnginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successfulsystemctl reload nginxDesde la recarga, GET y POST terminan en 403 sin llegar a PHP; el cuerpo de la respuesta es la página 403 por defecto de Nginx, de 153 bytes, y la respuesta tarda 0,02 segundos.
# Apache — .htaccess en la raíz del sitio (Apache 2.4)
<Files xmlrpc.php>
Require all denied
</Files>En un servidor que solo usa Apache, basta con poner la directiva Files de arriba en el .htaccess de la raíz del sitio para que se aplique al momento; la página 403 por defecto de Apache (199 bytes) vuelve en los mismos 0,02 segundos.
curl -s -o /dev/null -w '%{http_code}' -X POST https://example.com/xmlrpc.php -d '<methodCall><methodName>demo.sayHello</methodName></methodCall>'403El comando de arriba envía la llamada de prueba demo.sayHello por POST y comprueba el código de respuesta; tras bloquearlo en el servidor web devuelve 403. Si quitas las opciones [-o /dev/null] y [-w] verás el cuerpo: si llega [Hello!], está abierto, pero un sitio desactivado solo con el filtro responde lo mismo, así que hay que enviar también una llamada con inicio de sesión para confirmar el 405.
5️⃣ Cuándo no conviene desactivarlo y las alternativas
Bloquearlo por completo en el servidor web es lo más eficaz, pero no sirve para los sitios conectados a WordPress.com mediante Jetpack. Jetpack depende de la conexión XML-RPC, y en hostings que bloquean XML-RPC por defecto, como WP Engine, hay que abrir una excepción siguiendo las indicaciones de Jetpack para que conecte. Lo mismo ocurre con los sitios que quieren recibir pingbacks.
Estos sitios lo acotan permitiendo solo la lista de IP que publica Jetpack (jetpack.com/ips-v4.txt) y rechazando el resto. Dentro del bloque location de Nginx, añade una línea allow por cada rango de IP y termina con deny all.
Si no puedes bloquearlo por completo, queda limitar por minuto las solicitudes sin sesión. La configuración es la misma que la del apartado de límite de velocidad de Nginx del artículo Seguridad de wp-admin , y como solo cuenta las solicitudes sin cookie de sesión, no afecta al trabajo del administrador con sesión iniciada.
🔢 FAQ & contenido recomendado










ℹ️ Aviso de afiliados
El contenido de este sitio incluye enlaces de afiliado. Cuando un visitante compra un producto o servicio a través de uno de ellos, el sitio recibe una comisión del vendedor. El importe que paga el comprador(baja durante las promociones ↓)no aumenta. Los precios, descuentos y existencias publicados corresponden al momento de redacción y pueden variar, así que confírmalos con el vendedor antes de comprar. Los productos se eligen y evalúan con criterios propios, y las comisiones no influyen en el orden ni en el contenido de las reseñas.