XML-RPC do WordPress: 3 formas de desativar & como identificar ataques ao xmlrpc.php | Exceção do Jetpack

Se o log de acesso do seu site WordPress mostra centenas de requisições ao xmlrpc.php seguidas vindas do mesmo IP, é uma tentativa de adivinhar senhas pelo XML-RPC do WordPress. Classifiquei 8.624 registros de um dia de log de acesso de um servidor Nginx + Apache que hospeda vários sites WordPress, apliquei eu mesmo as três formas de desativar e conferi os códigos de resposta, e resumi em qual camada é preciso bloquear para reduzir também a execução de PHP.
O XML-RPC é um protocolo que troca documentos XML via HTTP para chamar funções do WordPress remotamente e, desde o WordPress 3.5, não existe mais a opção de ligar e desligar na tela de configurações, então ele já funciona ativado logo após a instalação.
1️⃣ O papel do xmlrpc.php e quem o usa
Depois que a REST API surgiu, é fácil achar que o [xmlrpc.php] é um arquivo que ninguém usa mais, mas o Jetpack ainda conecta o site ao WordPress.com por meio dele. A documentação oficial do Jetpack diz claramente que ele depende da conexão XML-RPC, e em hospedagens que bloqueiam o XML-RPC por padrão a própria conexão do Jetpack falha. É como um escritório que ainda mantém um número de fax.
No WordPress 7.1, esse arquivo aceita 77 métodos: 50 da família wp, que cuidam de posts, mídia e comentários, 23 dos antigos protocolos de blog (MetaWeblog, MovableType, Blogger), 2 de pingback e 2 demo para testes.
A família wp faz login com o nome de usuário e a senha que vão no corpo da requisição, por isso não passa pela tela de login. Escrever e editar posts, enviar mídia e gerenciar comentários são todos dessa família, e os 23 métodos dos antigos protocolos de blog foram mantidos para trocar posts do mesmo jeito que outras ferramentas de blog.
O demo.sayHello é uma chamada de teste que vem no XML-RPC do WordPress. Ao enviá-la, ele devolve só uma palavra, [Hello!], e, como não exige login, serve para verificar se o XML-RPC está funcionando. O par dele, demo.addTwoNumbers, soma dois números e devolve o resultado.
Todos só respondem a um corpo XML enviado por POST; se você abrir o endereço no navegador, volta um 405 com a mensagem [XML-RPC server accepts POST requests only.].
Os pingbacks também são função desse arquivo. Ao publicar um post, um pingback vai para o xmlrpc.php dos sites linkados no texto, e o servidor que recebe abre ele mesmo o endereço de origem para conferir se o link existe de fato.
2️⃣ Ataques ao xmlrpc.php vistos no log de acesso
A adivinhação de senhas não passa pela tela de login: entra pelo [xmlrpc.php]. No log de acesso de um servidor Nginx + Apache que hospeda vários sites WordPress, das 0h às 20h de 14 de outubro, foram 8.624 requisições POST /xmlrpc.php vindas de 72 IPs, e 19 desses IPs enviaram exatamente 300 cada um.
Um IP enviou 300 requisições em 28 minutos, das 14h09 às 14h37, com um intervalo de 5 a 6 segundos constante até o fim. 8.524 requisições chegaram com Jetpack ou WordPress.com no User-Agent, e o mesmo IP alternava vários valores que imitavam o formato real do Jetpack, como [Jetpack by WordPress.com (Jetpack 13.0; WordPress 6.3)].
Até as 14h daquele dia, a opção de desativar o XML-RPC do plugin de segurança mandava essas requisições de volta para a home com um 302; depois das 14h, com o bloqueio no Nginx, elas terminaram em 403. O 302 é resultado de o PHP rodar a cada requisição, mesmo com o corpo de 0 byte; o 403 é uma resposta que não passou pelo PHP.
3️⃣ system.multicall e adivinhação de senhas
Muitas explicações dizem que o [system.multicall] testa centenas de senhas numa só requisição, mas, quando um login falha uma vez dentro da requisição, o WordPress não compara mais as senhas das chamadas restantes. Só a primeira chamada passa pelo wp_authenticate; as seguintes recebem, sem verificação, o mesmo erro: [Incorrect username or password.].
Como volta uma resposta para cada chamada, uma única requisição com 100 senhas erradas recebe 100 erros numa resposta de 23 KB em 0,12 segundo. Se a senha certa estiver mais para o fim da mesma requisição, ela é recusada junto por causa da falha anterior; se estiver no começo, só essa chamada dá certo. A senha é comparada uma vez, mas a leitura do XML e o carregamento do WordPress se repetem a cada requisição, então a carga que esse ataque gera no servidor vem do número de requisições em si, e não da verificação de login.
4️⃣ 3 formas de desativar o XML-RPC e os códigos de resposta
Mesmo desativando o XML-RPC num plugin, as requisições ao [xmlrpc.php] chegam até o PHP. Com o filtro aplicado, enviei uma requisição com 100 chamadas e ainda assim voltaram HTTP 200 e 100 erros (24 KB). Os métodos se dividem em três, conforme a camada que recusa a requisição, e cada um tem um código de resposta diferente.
1. Desativar por código com o filtro xmlrpc_enabled
Basta colocar no functions.php do tema uma linha de add_filter que faça o filtro [xmlrpc_enabled] devolver false. Assim, as chamadas que exigem login são recusadas com o erro [XML-RPC services are disabled on this site.] e HTTP 405, mas a chamada de teste demo.sayHello, que não exige login, continua respondendo com 200.
2. Desativar nas configurações do plugin: Perfmatters · Hide My WP
É o método de desativar pela opção Disable XML-RPC de plugins como o Perfmatters e o Hide My WP. O Hide My WP usa o mesmo filtro de cima, então as respostas também são iguais, mas o Perfmatters, além do mesmo filtro, encerra com 403 as requisições diretas ao [xmlrpc.php] e ainda remove o cabeçalho e o link de pingback, de modo que, depois de desativado, tanto GET quanto POST respondem 403. Só que, para gerar esse 403, o WordPress roda até a etapa init, então a resposta leva 0,13–0,15 segundo, o mesmo tempo de quando está aberto, e a carga de PHP continua.
3. Bloquear no servidor web: Nginx · Apache
Seja no Nginx ou no Apache, a requisição é recusada antes do PHP, então, ao contrário dos métodos por plugin, o WordPress nem chega a rodar.
# Nginx — dentro do bloco server, acima do location que processa o PHP
location = /xmlrpc.php {
deny all;
}Esse bloco location fica acima do location que processa o PHP, e o Nginx recusa na hora as requisições que correspondem exatamente ao [xmlrpc.php]. Depois de incluí-lo, teste a configuração e recarregue para que passe a valer.
nginx -tnginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successfulsystemctl reload nginxA partir da recarga, GET e POST terminam em 403 sem chegar ao PHP; o corpo da resposta é a página 403 padrão do Nginx, de 153 bytes, e a resposta leva 0,02 segundo.
# Apache — .htaccess na raiz do site (Apache 2.4)
<Files xmlrpc.php>
Require all denied
</Files>Num servidor só com Apache, basta colocar a diretiva Files acima no .htaccess da raiz do site para valer na hora, e a página 403 padrão do Apache (199 bytes) volta nos mesmos 0,02 segundo.
curl -s -o /dev/null -w '%{http_code}' -X POST https://example.com/xmlrpc.php -d '<methodCall><methodName>demo.sayHello</methodName></methodCall>'403O comando acima envia a chamada de teste demo.sayHello por POST e confere o código de resposta; depois do bloqueio no servidor web, volta 403. Se você enviar sem as opções [-o /dev/null] e [-w], aparece o corpo: se vier [Hello!], está aberto. Um site desativado só pelo filtro dá a mesma resposta, então é preciso enviar também uma chamada de login para confirmar o 405.
5️⃣ Quando não desativar e as alternativas
Bloquear tudo no servidor web é o método de maior efeito, mas não serve para sites conectados ao WordPress.com pelo Jetpack. O Jetpack depende da conexão XML-RPC e, em hospedagens que bloqueiam o XML-RPC por padrão, como a WP Engine, é preciso abrir uma exceção seguindo as orientações do Jetpack para conectar. O mesmo vale para sites que querem receber pingbacks.
Esses sites restringem o acesso liberando só a lista de IPs que o Jetpack publica (jetpack.com/ips-v4.txt) e recusando o resto. Basta colocar no bloco location do Nginx uma linha allow por faixa de IP e terminar com deny all.
Se não der para bloquear de vez, resta aplicar um limite por minuto às requisições sem login. A configuração é a mesma da seção de limite de taxa do Nginx no artigo Segurança do wp-admin e, como conta só as requisições sem cookie de login, não afeta o trabalho de administração feito com login.
🔢 FAQ & conteúdo recomendado










ℹ️ Aviso de afiliados
O conteúdo deste site contém links de afiliados. Quando um visitante compra um produto ou serviço por meio de um desses links, o site recebe uma comissão do vendedor. O valor que o comprador paga (em promoções, ele diminui ↓) não aumenta por causa disso. Preços, descontos e informações de estoque valem para o momento em que o texto foi escrito e podem ter mudado, por isso confirme com o vendedor antes de comprar. Os produtos são escolhidos e avaliados por critérios próprios, e o recebimento de comissão não influencia a ordem de apresentação nem o conteúdo das avaliações.