InfraFenceInfraFence

Live Security Operations

Commencer

Comment bloquer les attaques par force brute sur xmlrpc.php dans WordPress

Dernière mise à jour:

xmlrpc.php est l'ancienne API distante de WordPress. Peu de sites en ont encore besoin, et chaque site WordPress d'internet est attaqué par ce biais. Sa méthode system.multicall exécute de nombreux appels en une seule requête : un seul POST peut essayer des centaines de mots de passe, et c'est pourquoi les limiteurs de connexion qui comptent les requêtes vers wp-login.php ne le voient pas. Sa méthode pingback.ping sert à faire envoyer par votre site des requêtes vers celui de quelqu'un d'autre.

Ci-dessous : comment reconnaître l'attaque dans les logs d'accès, comment bloquer xmlrpc.php dans nginx, Apache ou WordPress lui-même, que faire si vous utilisez Jetpack, et comment limiter le débit ou bannir avec fail2ban quand vous devez le laisser ouvert. Les commandes sont pour Debian et Ubuntu et pour la famille RHEL ; example.com désigne votre site.

Reconnaître une attaque XML-RPC dans les logs d'accès

Comptez les requêtes POST vers xmlrpc.php par adresse. Un site normal en voit quelques-unes par jour, venant de Jetpack ou d'une application. Des centaines depuis une seule adresse, ou un flux régulier depuis de nombreuses adresses, c'est une attaque.

Toutes distributions
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

La commande lit les emplacements habituels : nginx, Apache sur Debian et Ubuntu, httpd sur la famille RHEL, et les logs par domaine de cPanel. Les requêtes par heure montrent quand l'attaque a commencé :

Toutes distributions
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
  • Le statut est 200 même pour un mauvais mot de passe : XML-RPC signale l'échec à l'intérieur de la réponse. Ne lisez pas 200 comme une connexion réussie.
  • Des réponses volumineuses, répétées depuis une même adresse toutes les quelques secondes, sont en général du system.multicall : une seule réponse pour de nombreuses tentatives de mot de passe.
  • Le log ne montre pas quelle méthode a été appelée : le corps de la requête n'est pas journalisé. Force brute et abus de pingback se ressemblent ici.

Pour savoir si XML-RPC répond sur un site, demandez-lui la liste de ses méthodes depuis n'importe quelle machine. Une liste de méthodes signifie qu'il est actif ; un 403 signifie qu'il est bloqué.

Toute machine avec 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

Bloquer xmlrpc.php dans nginx ou Apache

Bloquer au niveau du serveur web est l'option la moins coûteuse : la requête est refusée avant que PHP et WordPress ne se chargent. Dans nginx, ajoutez ceci au bloc server de chaque site WordPress :

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

Dans Apache 2.4, placez ceci dans le .htaccess du site (pas besoin de recharger ; LiteSpeed le lit aussi) ou dans son <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

Attention: Bloquer xmlrpc.php casse ce qui l'utilise encore : Jetpack (voir plus bas), les pingbacks, et certaines applications et outils de publication anciens. En cas de doute, cherchez d'abord les appelants légitimes dans les logs de l'étape 1.

Désactiver XML-RPC dans WordPress

Si vous ne pouvez pas modifier la configuration du serveur web, une extension must-use le fait depuis WordPress. Les extensions must-use se chargent à chaque requête et ne peuvent pas être désactivées par erreur depuis l'administration. Remplacez le chemin par celui de votre site :

Toutes distributions
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

Remarque: Le filtre xmlrpc_enabled ne désactive que les méthodes qui demandent une connexion, comme le dit la référence WordPress ; le second filtre retire les méthodes de pingback. PHP s'exécute quand même à chaque requête : sous une attaque intense, le blocage au niveau du serveur web ci-dessus est plus léger.

Des extensions font la même chose : des extensions dédiées « Disable XML-RPC », et des extensions de sécurité comme Wordfence et Solid Security ont une option pour cela (en novembre 2026). Une extension se désactive facilement par accident lors d'une mise à jour ; l'extension must-use et la règle du serveur web, non.

Garder Jetpack fonctionnel

Jetpack communique avec votre site via xmlrpc.php : le bloquer pour tout le monde casse la connexion Jetpack. Jetpack publie les plages d'adresses de ses serveurs sur sa page de liste d'autorisation : autorisez-les et bloquez tous les autres. Dans nginx, le bloc location doit alors aussi transmettre la requête à 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;
}

Copiez les lignes PHP du bloc location ~ \.php$ du site lui-même : le chemin du socket varie d'un système à l'autre. Dans Apache, Require ip seul n'autorise que ces plages :

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>

Remarque: Plages telles que listées par Jetpack en novembre 2026. Elles peuvent changer : vérifiez la page de temps en temps. Derrière Cloudflare ou un autre proxy, ces règles voient l'adresse du proxy, sauf si l'adresse réelle du visiteur est restaurée d'abord (real_ip de nginx, mod_remoteip d'Apache).

Limiter le débit de xmlrpc.php quand il doit rester ouvert

Si une application ou un service que vous ne pouvez pas lister par adresse a besoin de XML-RPC, limitez la fréquence à laquelle chaque adresse peut l'appeler. Dans nginx, définissez une zone dans le contexte http, puis utilisez-la pour xmlrpc.php : ici 6 requêtes par minute et par adresse, avec une rafale de 5, et 429 au-delà.

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;
}
Toutes distributions
sudo nginx -t && sudo systemctl reload nginx

Remarque: Une limite de débit compte les requêtes, pas les mots de passe : une seule requête system.multicall dans la limite peut encore contenir des centaines de tentatives. Associez-la à des mots de passe robustes, ou bannissez les adresses avec fail2ban. Apache n'a pas d'équivalent intégré ; utilisez fail2ban ci-dessous.

Bannir les attaquants XML-RPC avec fail2ban

fail2ban lit le log d'accès et bannit une adresse au niveau du pare-feu : il fonctionne aussi bien avec nginx qu'avec Apache. Installez-le comme dans Comment stopper les attaques par force brute SSH (dépôts Debian et Ubuntu, EPEL sur la famille RHEL). Créez ensuite un filtre pour les requêtes POST vers xmlrpc.php :

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

Puis une jail : 5 requêtes en 60 secondes, un bannissement d'une heure. Mettez votre propre adresse dans ignoreip et, si vous utilisez Jetpack, ses plages. Pour Apache, réglez logpath sur /var/log/apache2/*access.log sur Debian et Ubuntu ou sur /var/log/httpd/*access_log sur la famille RHEL.

Toutes distributions
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

Testez le filtre sur le vrai log avant de l'activer : fail2ban-regex indique combien de lignes correspondent. Redémarrez ensuite fail2ban et vérifiez la jail.

Toutes distributions
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

Attention: Derrière Cloudflare ou un autre proxy, le log d'accès montre l'adresse du proxy : fail2ban bannirait le proxy et rendrait le site inaccessible à de nombreux visiteurs. Restaurez d'abord l'adresse réelle dans le log, ou bloquez au niveau du proxy.

Comment InfraFence gère les attaques XML-RPC

Le pare-feu web d'InfraFence lit les logs d'accès de nginx, Apache et LiteSpeed, et depuis l'agent 1.0.42 aussi ceux de Traefik, Caddy, HAProxy et des serveurs web en conteneurs. Derrière Cloudflare, il prend l'adresse du visiteur dans CF-Connecting-IP, et uniquement depuis les plages de Cloudflare.

  • Toutes les 5 requêtes POST vers xmlrpc.php venant d'une même adresse sur un même site en moins d'une minute ajoutent 25 points au score de cette adresse. Une attaque soutenue continue d'en ajouter.
  • Le score cumule aussi les autres attaques de l'adresse, comme la force brute sur wp-login.php, le scan d'extensions ou les tentatives d'exploit, et baisse de 5 points par minute : une requête isolée et étrange s'efface d'elle-même.
  • À 80 points, l'adresse est bannie au niveau du pare-feu et l'événement est signalé comme critique. Seuils et points se modifient par serveur, et le mode surveillance signale sans bannir.

Voir Pare-feu web pour les règles et le score. Ce qu'il ne fait pas : il compte les requêtes, il ne peut donc pas voir combien de mots de passe contient une requête system.multicall ; il ne modifie pas votre configuration nginx, Apache ou WordPress, donc xmlrpc.php reste ouvert tant que vous ne le bloquez pas comme ci-dessus ; et il n'a pas d'exception intégrée pour Jetpack : si les serveurs de Jetpack appellent assez souvent pour être notés, ajoutez leurs plages à la liste blanche. Le bannissement vient du log : la requête qui l'a déclenché a déjà reçu sa réponse.

Questions

Peut-on désactiver xmlrpc.php sans risque ?
Pour la plupart des sites, oui : l'éditeur de blocs et l'administration utilisent l'API REST, pas XML-RPC. Vérifiez d'abord si vous utilisez Jetpack, les pingbacks, une application ou un outil de publication qui en dépend encore.
Pourquoi les attaques affichent-elles le statut 200 ?
XML-RPC répond 200 et signale l'échec de connexion à l'intérieur de la réponse. Le code de statut ne dit rien sur la validité d'un mot de passe.
Mon extension de limitation des connexions ne l'arrête pas. Pourquoi ?
Beaucoup de limiteurs comptent les tentatives sur wp-login.php. Une requête system.multicall via xmlrpc.php contient des centaines de tentatives en une seule requête et les contourne. Bloquez ou limitez xmlrpc.php lui-même.

Essayez gratuitement sur 1 serveur

Starter est gratuit pour un serveur, avec la protection complète et le blocage automatique. Sans carte bancaire.

Installez l'agent en root, avec votre jeton à la place de <TOKEN> :

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

Le jeton se crée dans le tableau de bord, dans Serveurs › Ajouter un serveur.