Bloquer xmlrpc.php sur WordPress : pourquoi et comment

Credit : Logo officiel

Bloquer xmlrpc.php sur WordPress : pourquoi et comment

Dylan D. — Agent Support Technique Serveur Securite 1925 mots 10 min de lecture

Bloquer xmlrpc.php sur WordPress : pourquoi et comment

Un jeudi matin, vers 10h, je reçois un ticket d'un client en mutualisé IONOS. Symptôme : son site WordPress est lent depuis trois jours, parfois carrément inaccessible avec des erreurs 503. La sauvegarde nocturne échoue. Je me connecte en SSH, je tape tail -f access.log sur son hébergement et là je vois défiler à toute vitesse : POST /xmlrpc.php depuis des centaines d'IPs différentes, plusieurs fois par seconde. En 24 heures il avait reçu 47 000 requêtes sur ce seul fichier. Le serveur saturait à essayer de répondre.

C'est devenu tellement courant que je le mets dans ma checklist d'audit systématique : si tu utilises WordPress et que tu n'as pas explicitement besoin de XML-RPC, bloque-le. Cinq minutes de configuration pour économiser des heures de débogage. Voici tout ce qu'il faut savoir.

XML-RPC : c'est quoi exactement ?

XML-RPC est un protocole d'appel de procédure à distance qui utilise XML pour encoder les requêtes et HTTP comme transport. Dans WordPress, le fichier xmlrpc.php à la racine sert de point d'entrée à ce protocole. Il existe depuis WordPress 1.5, soit 2005.

À l'origine, il était utilisé pour :

Le problème ? Depuis WordPress 4.7 sortie en décembre 2016, l'API REST WordPress (/wp-json/) remplace XML-RPC pour la quasi-totalité des usages modernes. L'application mobile WordPress utilise désormais l'API REST. Jetpack aussi pour ses fonctions principales. Les nouveaux plugins de backup passent par l'API REST ou des connexions directes SSH/SFTP.

XML-RPC reste activé par défaut pour des raisons de rétrocompatibilité, pas parce qu'il est utile. Et c'est là que le bât blesse.

Pourquoi xmlrpc.php est une cible privilégiée

Brute force amplifié avec system.multicall

C'est la raison principale pour laquelle les attaquants adorent ce fichier. La méthode system.multicall permet de batcher plusieurs requêtes XML-RPC en une seule connexion HTTP. Concrètement, un attaquant peut envoyer une requête contenant 500 tentatives de login dans un seul POST, contourner la plupart des protections rate-limit, et tester 500 combinaisons login/mot de passe en un seul aller-retour réseau.

Là où une attaque sur /wp-login.php déclenche Fail2ban, Wordfence ou CrowdSec après 5 à 10 tentatives, l'attaque sur xmlrpc.php peut faire 500 essais avant d'être détectée par les outils mal configurés. Et même quand elle l'est, le mal est fait : le CPU a déjà cramé pour traiter ces 500 vérifications de mot de passe (chaque vérification déclenche un hash bcrypt qui consomme du CPU).

Voici à quoi ressemble une requête system.multicall malveillante :

<?xml version="1.0"?>
<methodCall>
  <methodName>system.multicall</methodName>
  <params>
    <param>
      <value><array><data>
        <value><struct>
          <member><name>methodName</name><value>wp.getUsersBlogs</value></member>
          <member><name>params</name><value><array><data>
            <value><array><data>
              <value><string>admin</string></value>
              <value><string>password123</string></value>
            </data></array></value>
          </data></array></value></member>
        </struct></value>
        <!-- Répété 500 fois avec des combinaisons différentes -->
      </data></array></value>
    </param>
  </params>
</methodCall>

Pingback DDoS amplification

L'autre vecteur d'attaque, c'est la méthode pingback.ping. Un attaquant peut demander à ton serveur WordPress de faire une requête HTTP vers une URL arbitraire, prétendument pour vérifier un pingback. En coordonnant des milliers de WordPress vulnérables, on obtient une attaque DDoS distribuée où chaque WordPress devient un relais d'attaque vers la cible finale.

C'est exactement ce qui s'est passé en 2014 quand Sucuri a documenté une attaque DDoS impliquant 162 000 sites WordPress utilisés comme relais. Ton site peut servir d'arme contre quelqu'un d'autre sans que tu le saches.

Consommation de ressources même sans succès

Même si l'attaque échoue (mauvais mot de passe), elle a coûté au serveur :

Sur un mutualisé IONOS où les ressources sont partagées et limitées, ça peut faire passer le site en 503 simplement parce que les workers PHP sont tous occupés à traiter des attaques.

Détecter une attaque en cours

Avant de bloquer, vérifie si tu es ciblé. Sur un VPS ou dédié IONOS :

cd /var/log/nginx
grep -c xmlrpc access.log
grep xmlrpc access.log | tail -20
grep xmlrpc access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -20

La première commande compte les requêtes totales. La deuxième te montre les 20 dernières. La troisième te donne le top 20 des IPs qui frappent le fichier, avec le nombre de requêtes par IP.

Sur un mutualisé IONOS sans accès SSH direct, va dans Espace client > Hébergement > Statistiques > Logs. Cherche xmlrpc dans les logs des dernières 24h.

Règle empirique : si tu vois plus de 100 POST sur xmlrpc.php par jour alors que tu n'utilises pas activement la fonctionnalité, t'es ciblé. Au-delà de 1000, c'est une attaque automatisée en cours.

Solution 1 : bloquer via .htaccess (Apache, mutualisé IONOS)

C'est la méthode que je recommande à 90% des clients parce qu'elle marche partout, elle est simple, et elle bloque la requête avant que PHP soit exécuté. Le serveur web renvoie un 403 sans charger WordPress, ce qui consomme quasi zéro CPU.

Dans le .htaccess à la racine du site, avant les règles WordPress (# BEGIN WordPress) :

<Files xmlrpc.php>
    Require all denied
</Files>

C'est la syntaxe Apache 2.4+. Si tu es sur du vieux Apache 2.2 (rare aujourd'hui même chez IONOS) :

<Files xmlrpc.php>
    Order deny,allow
    Deny from all
</Files>

Variante avec une RewriteRule qui retourne un 403 explicite :

RewriteEngine On
RewriteRule ^xmlrpc\.php$ - [F,L]

Le flag F retourne un 403 Forbidden. Le flag L arrête le traitement. Cette méthode a un avantage subtil : elle peut être combinée avec des conditions, par exemple n'autoriser xmlrpc.php que depuis une IP spécifique :

RewriteEngine On
RewriteCond %{REMOTE_ADDR} !^123\.45\.67\.89$
RewriteRule ^xmlrpc\.php$ - [F,L]

Solution 2 : bloquer via Nginx

Sur un VPS IONOS avec Nginx, dans le bloc server de ta config :

location = /xmlrpc.php {
    deny all;
    access_log off;
    log_not_found off;
    return 403;
}

Le access_log off évite de polluer tes logs avec les milliers de tentatives bloquées. Le log_not_found off fait pareil pour les logs d'erreur. Tu peux toujours réactiver temporairement si tu veux mesurer l'attaque.

Pour autoriser quelques IPs spécifiques (par exemple si tu utilises encore Jetpack qui a besoin de XML-RPC pour certaines fonctions) :

location = /xmlrpc.php {
    allow 122.248.245.244/32;
    allow 54.217.201.243/32;
    allow 54.232.116.4/32;
    allow 192.0.80.0/20;
    allow 192.0.96.0/20;
    allow 192.0.112.0/20;
    allow 195.234.108.0/22;
    deny all;
    
    include fastcgi_params;
    fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

Les plages IP ci-dessus sont celles de Jetpack/Automattic au moment où j'écris cet article. Vérifie sur jetpack.com/support/hosting-faq/ pour la liste à jour.

Après modification, recharge Nginx :

sudo nginx -t && sudo systemctl reload nginx

Solution 3 : désactiver via PHP/WordPress

C'est la solution moins efficace parce qu'elle laisse PHP s'exécuter, donc elle consomme quand même du CPU. Mais elle reste utile en complément, ou quand tu n'as pas accès au serveur web.

Dans functions.php du thème actif (mieux : dans un mu-plugin) :

<?php
// Désactive complètement XML-RPC
add_filter('xmlrpc_enabled', '__return_false');

// Supprime les méthodes XML-RPC de pingback
add_filter('xmlrpc_methods', function($methods) {
    unset($methods['pingback.ping']);
    unset($methods['pingback.extensions.getPingbacks']);
    return $methods;
});

// Supprime le header X-Pingback
add_filter('wp_headers', function($headers) {
    unset($headers['X-Pingback']);
    return $headers;
});

Le plus propre c'est de mettre ça dans wp-content/mu-plugins/disable-xmlrpc.php. Comme ça c'est actif quel que soit le thème, et même un changement de thème ne réactive pas XML-RPC.

Désactiver les pingbacks au niveau global

En complément, je désactive systématiquement les pingbacks via WP-CLI :

wp option update default_pingback_flag 0
wp option update default_ping_status closed

La première commande désactive l'envoi de pingbacks pour les nouveaux articles. La seconde désactive la réception des pingbacks. Pour les articles existants :

wp post list --post_type=post --format=ids | xargs wp post update --ping_status=closed

Erreurs courantes et leur fix

Erreur 1 : bloquer xmlrpc.php casse Jetpack

Client bloque, le lendemain Jetpack ne synchronise plus, les statistiques Jetpack tombent à zéro. Solution : whitelist les IPs Jetpack (voir la config Nginx ci-dessus) ou utilise la méthode PHP qui désactive XML-RPC mais laisse Jetpack utiliser ses connexions API directes.

Erreur 2 : règle .htaccess placée après les règles WordPress

Le .htaccess est lu de haut en bas. Si tu mets ton blocage après la section # BEGIN WordPress, les règles de réécriture WordPress vont matcher en premier et ton blocage ne se déclenchera jamais. Place toujours le blocage avant la section WordPress, en haut du fichier.

Erreur 3 : le client utilise vraiment l'app WordPress mobile

L'app WordPress depuis 2020 utilise principalement l'API REST, mais certaines anciennes versions ou certaines fonctions tombent encore en fallback sur XML-RPC. Avant de bloquer, demande au client : utilises-tu l'app mobile pour publier ? Si oui, fais-lui mettre à jour l'app et teste après blocage.

Erreur 4 : caching plugin qui sert une version cachée du 403

WP Rocket, W3 Total Cache, ou même le cache Cloudflare peuvent servir une version cachée de la réponse 403 même après que tu aies retiré le blocage. Si tu changes d'avis et veux réactiver xmlrpc.php, purge tous les caches.

Erreur 5 : fichier xmlrpc.php restauré par une mise à jour

Certains clients suppriment carrément le fichier. Mauvaise idée : la prochaine mise à jour du core WordPress va le restaurer. Toujours bloquer via configuration serveur, jamais en supprimant le fichier physiquement.

Cas concret : retour sur le client de jeudi matin

Reprenons l'histoire du début. Sur l'hébergement IONOS du client, j'ai :

  1. Confirmé l'attaque avec grep -c xmlrpc /var/log/nginx/access.log qui a renvoyé 47 312 sur 24h
  2. Identifié les IPs sources (200+ IPs réparties sur des botnets)
  3. Ajouté le blocage .htaccess ci-dessus
  4. Activé Fail2ban en complément avec un jail sur les autres patterns d'attaque

Résultat sur 24h : la charge CPU moyenne du compte est passée de 87% à 12%. Les requêtes 503 ont disparu. La sauvegarde nocturne a réussi pour la première fois en trois jours. Le client était content. Surtout, il n'utilisait absolument pas XML-RPC : ni app mobile, ni Jetpack, ni outil externe. Le fichier était juste là par défaut, ouvert sur internet, à attendre les bots.

Pour aller plus loin

La sécurité WordPress c'est un ensemble de petites mesures qui s'empilent. Voici les sujets connexes que je recommande chaudement :

Le réflexe à adopter dès l'installation

J'ai un script d'installation WordPress que j'utilise pour tous mes clients VPS. Une des premières étapes après l'installation, c'est d'ajouter le blocage xmlrpc.php dans la config Nginx. Avant même que le client se connecte au back-office. Pourquoi ? Parce que les bots scannent constamment internet, et un WordPress fraîchement installé peut recevoir sa première attaque XML-RPC dans l'heure qui suit la mise en ligne.

C'est devenu un réflexe : nouveau WordPress = blocage xmlrpc.php immédiat, sauf demande explicite contraire du client. Ça fait gagner du temps à tout le monde, ça consomme rien comme ressources, et ça évite des tickets de support pénibles plus tard. La cybersécurité c'est aussi ça : couper les portes inutilisées avant que quelqu'un essaie de les enfoncer.

# Articles similaires

Sur les memes sujets et plus loin