Блог

xmlrpc.php: дыра, которая открыта на 80% WordPress-сайтов прямо сейчас.

xmlrpc
Wordpress / xmlrpc

xmlrpc.php: дыра, которая открыта на 80% WordPress-сайтов прямо сейчас.

Воскресенье, 2 часа ночи. Сервер с WordPress-магазином начинает тормозить. PHP-FPM упирается в лимит воркеров, CPU под 100%, сайт не отвечает. Смотришь access.log — и видишь это:

185.220.101.42 - - [26/May/2026:02:13:44 +0300] "POST /xmlrpc.php HTTP/1.1" 200 674
185.220.101.43 - - [26/May/2026:02:13:44 +0300] "POST /xmlrpc.php HTTP/1.1" 200 674
185.220.101.44 - - [26/May/2026:02:13:44 +0300] "POST /xmlrpc.php HTTP/1.1" 200 674
185.220.101.45 - - [26/May/2026:02:13:44 +0300] "POST /xmlrpc.php HTTP/1.1" 200 674

Тысячи строк в секунду. Разные IP, одна цель. Это не DDoS на полосу пропускания — это атака на xmlrpc.php, и сервер послушно обрабатывает каждый запрос, тратя CPU на PHP.

Если ты администрируешь WordPress-сайты и ещё не закрыл xmlrpc.php — читай дальше.

ЧТО ТАКОЕ xmlrpc.php И ЗАЧЕМ ОН ВООБЩЕ СУЩЕСТВУЕТ

XML-RPC — это протокол удалённого вызова процедур поверх HTTP, появился ещё в 1998 году. WordPress добавил его для того, чтобы сторонние приложения могли управлять сайтом без браузера: публиковать посты из Windows Live Writer, синхронизировать контент через мобильные приложения, интегрироваться с внешними сервисами.

Запрос через xmlrpc.php выглядит так:

POST /xmlrpc.php HTTP/1.1
Host: example.com
Content-Type: text/xml

<?xml version="1.0"?>
<methodCall>
  <methodName>wp.getUsersBlogs</methodName>
  <params>
    <param><value>admin</value></param>
    <param><value>password123</value></param>
  </params>
</methodCall>

WordPress отвечает — и при верных credentials — возвращает полный доступ к сайту.

Проверить, открыт ли xmlrpc.php на твоём сайте, просто:

curl -s https://example.com/xmlrpc.php

Если в ответе видишь:

XML-RPC server accepts POST requests only.

— поздравляю, xmlrpc.php активен и доступен снаружи. Именно этот ответ боты используют как разведку.

ТРИ СПОСОБА СЛОМАТЬ САЙТ ЧЕРЕЗ xmlrpc.php

1. Брутфорс с амплификацией через system.multicall

Классический брутфорс на wp-login.php легко блокировать — один запрос, одна попытка. С xmlrpc.php всё интереснее: метод system.multicall позволяет упаковать несколько вызовов в один HTTP-запрос.

До WordPress 4.4 один запрос мог содержать тысячи попыток подобрать пароль. После патча в декабре 2015 года WordPress прерывает пакет после первой неудачной аутентификации — но это не значит, что атака исчезла. Боты просто адаптировались: теперь они шлют сотни последовательных запросов с разных IP из ботнета, обходя IP-based rate limiting.

Payload выглядит так:

<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>admin</value>
            <value>password1</value>
          </data></array></value>
        </member>
      </struct></value>
      <!-- ещё 499 попыток -->
    </data></array></value></param>
  </params>
</methodCall>

Итог: каждый запрос — это не одна попытка, а десятки. PHP воркер занят на всё это время. Даже с патчем WordPress — нагрузка на сервер кратно выше, чем от атаки на wp-login.php.

2. DDoS-амплификация через pingback

Это атака на чужие сайты с твоего сервера. Злоумышленник отправляет на твой (уязвимый) WordPress запрос:

<methodCall>
  <methodName>pingback.ping</methodName>
  <params>
    <param><value><string>http://ЖЕРТВА.com/</string></value></param>
    <param><value><string>https://твой-сайт.com/любой-пост/</string></value></param>
  </params>
</methodCall>

Твой WordPress послушно делает HTTP-запрос к сайту жертвы. Если таких WordPress-сайтов тысяча — жертва получает тысячу запросов одновременно. Твой сервер становится участником DDoS-атаки, и твой IP попадает в блэклисты.

Именно так была атакована сеть сайтов DoD (Министерства обороны США) — через незакрытый xmlrpc.php на их же WordPress-ресурсах.

3. Сканирование портов внутренней сети (SSRF через pingback)

Pingback принимает произвольный URL, включая внутренние адреса. Атакующий может сканировать внутреннюю инфраструктуру через твой сервер:

<!-- попытка достучаться до внутренних сервисов -->
<value><string>http://192.168.1.1:8080/</string></value>
<value><string>http://10.0.0.1:3306/</string></value>

По разнице во времени ответа и коду ошибки можно понять, какие порты открыты внутри.

КАК ЗАКРЫТЬ: ТРИ УРОВНЯ ЗАЩИТЫ

Уровень 1: nginx — блокировать на уровне веб-сервера

Это самый быстрый и эффективный способ. Запрос не доходит до PHP вообще.

Если xmlrpc.php не нужен совсем (большинство случаев):

# В блоке server{} твоего nginx-конфига
location = /xmlrpc.php {
    deny all;
    access_log off;
    log_not_found off;
}

Если нужен доступ только с конкретных IP (например, Jetpack или WooCommerce мобильное приложение):

location = /xmlrpc.php {
    allow 198.51.100.10;    # IP твоего мобильного приложения
    allow 198.51.100.11;    # IP Jetpack servers
    deny all;
    access_log off;
}

После изменения — проверить конфиг и перезагрузить:

nginx -t && systemctl reload nginx

Проверить что блокировка работает:

curl -I https://example.com/xmlrpc.php
# Ожидаемый ответ: HTTP/1.1 403 Forbidden

Уровень 2: fail2ban — банить тех, кто всё равно пытается

Даже с блокировкой в nginx — боты продолжат стучать, получать 403 и уходить. Но стучать они будут постоянно, засоряя логи. fail2ban добавит их в чёрный список на уровне nftables.

Создать фильтр:

cat > /etc/fail2ban/filter.d/nginx-xmlrpc.conf << 'EOF'
[Definition]
failregex = ^<HOST> - - \[.*\] "(GET|POST) /xmlrpc\.php HTTP/.*" (200|301|302|403|404) \d+ ".*" ".*"
ignoreregex =
EOF

Убедиться, что fail2ban использует nftables как backend (по умолчанию на современных системах это уже так, но лучше проверить):

grep -r "banaction" /etc/fail2ban/jail.conf /etc/fail2ban/jail.local 2>/dev/null | grep -v "^#"
# Должно быть: banaction = nftables-multiport
# Если там iptables — добавь в /etc/fail2ban/jail.local:
# [DEFAULT]
# banaction = nftables-multiport

Создать jail:

cat > /etc/fail2ban/jail.d/nginx-xmlrpc.conf << 'EOF'

[nginx-xmlrpc]

enabled = true port = http,https filter = nginx-xmlrpc logpath = /var/log/nginx/access.log maxretry = 3 findtime = 60 bantime = 86400 action = nftables-multiport[name=xmlrpc, port=»80,443″, protocol=tcp] EOF

Перезапустить fail2ban:

systemctl restart fail2ban
fail2ban-client status nginx-xmlrpc

Уровень 3: Cloudflare WAF (если сайт за Cloudflare)

Если сайт идёт через Cloudflare, настроить Rate Limiting Rule в дашборде:

Security → WAF → Rate limiting rules → Create rule:

Field: URI Path
Operator: contains
Value: /xmlrpc.php

Action: Block
Duration: 1 hour
Threshold: 5 requests per 30 seconds per IP

На Pro и выше доступно managed WAF-правило WP0018, которое автоматически детектирует system.multicall паттерны.

Важный нюанс: если сайт за Cloudflare, в nginx access.log ты видишь IP Cloudflare, а не реальный IP атакующего. Убедись что в nginx настроен real_ip:

# В nginx.conf, блок http{}
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2a06:98c0::/29;
set_real_ip_from 2c0f:f248::/32;
real_ip_header CF-Connecting-IP;

А ЕСЛИ xmlrpc.php НУЖЕН (Jetpack, WooCommerce Mobile)

Jetpack до сих пор использует XML-RPC для связи с WordPress.com. Если отключить полностью — Jetpack сломается. Правильный подход:

  1. Получить актуальные IP-диапазоны Jetpack и разрешить только их. IP меняются — официальная документация Jetpack явно предупреждает об этом и рекомендует автоматизировать обновление. Актуальный список всегда доступен машиночитаемым образом:
# Посмотреть текущие IP-диапазоны Jetpack
curl -s https://jetpack.com/ips-v4.json | python3 -m json.tool

# Пример конфига на основе актуального списка (jetpack.com/support/how-to-add-jetpack-ips-allowlist)
location = /xmlrpc.php {
    # Актуальные Jetpack IP ranges — проверяй на jetpack.com/ips-v4.json
    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 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;
}

Важно: этот список меняется. Не прописывай IP вручную раз и навсегда — лучше автоматизировать обновление через systemd timer или cron.

  1. Добавить rate limiting даже для разрешённых IP:
limit_req_zone $binary_remote_addr zone=xmlrpc:10m rate=5r/m;

location = /xmlrpc.php {
    limit_req zone=xmlrpc burst=3 nodelay;
    allow 192.0.64.0/18;
    # ... остальные Jetpack диапазоны
    deny all;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

ДИАГНОСТИКА: КАК ПОНЯТЬ ЧТО ТЫ УЖЕ ПОД АТАКОЙ

Проверить количество запросов к xmlrpc.php за последний час:

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

Посмотреть топ атакующих IP:

grep "POST /xmlrpc.php" /var/log/nginx/access.log | \
  awk '{print $1}' | \
  sort | uniq -c | sort -rn | head -20

Проверить нагрузку от PHP-FPM:

systemctl status php8.3-fpm
# Или напрямую:
ps aux | grep php-fpm | wc -l

Если видишь десятки PHP-воркеров при минимальном трафике — xmlrpc.php скорее всего уже нагружает сервер.

ТИПИЧНЫЕ ОШИБКИ

Ошибка 1: «Я поставил плагин Disable XML-RPC — теперь безопасно»

Плагин отключает XML-RPC на уровне WordPress, но запрос всё равно доходит до PHP, PHP инициализирует WordPress, подключает плагины, и только потом возвращает пустой ответ. Сервер всё равно тратит ресурсы. Правильный способ — блокировка в nginx до PHP.

Ошибка 2: «У меня нет WordPress, только PHP-приложение»

xmlrpc.php специфичен для WordPress. Если WordPress нет — проблемы нет. Но проверить стоит: иногда WordPress устанавливают в подкаталог и забывают.

Ошибка 3: «Блокирую по IP — атакующих много, не успеваю»

IP-based блокировка не работает против распределённых ботнетов — IP меняются постоянно. Правильный подход: блокировать endpoint целиком (location = /xmlrpc.php { deny all; }), а не пытаться угнаться за IP.

Ошибка 4: «fail2ban забанил, но в логах всё равно появляются запросы»

fail2ban работает реактивно — сначала запрос обрабатывается, потом IP банится. Если атака распределённая (каждый IP делает по 2 запроса), fail2ban не поможет. Решение — комбинация nginx deny all для endpoint + Cloudflare WAF на верхнем уровне.

ПРОВЕРЬ СВОИ САЙТЫ ПРЯМО СЕЙЧАС

# Быстрая проверка — открыт ли xmlrpc.php
curl -s -o /dev/null -w "%{http_code}" https://твой-сайт.com/xmlrpc.php
# 200 или "XML-RPC server accepts POST requests only" = открыт, надо закрыть
# 403 = закрыт nginx
# 404 = файл не найден (WordPress не установлен или путь другой)

# Проверить все WordPress-сайты на сервере
for conf in /etc/nginx/sites-enabled/*; do
  domain=$(grep server_name "$conf" | head -1 | awk '{print $2}' | tr -d ';')
  code=$(curl -s -o /dev/null -w "%{http_code}" "https://$domain/xmlrpc.php" 2>/dev/null)
  echo "$domain: $code"
done

ИТОГ

xmlrpc.php — это легаси из 1998 года, который WordPress тащит за собой до сих пор. В 2026 году подавляющему большинству сайтов он не нужен. Но он включён по умолчанию, доступен снаружи, и боты его знают.

Три минуты в nginx-конфиге закрывают проблему полностью:

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

Если нужен Jetpack — белый список IP вместо полного запрета. Сверху — fail2ban для логирования и бана тех, кто всё равно стучит. И Cloudflare WAF если сайт за проксированием.

Проверь свои сайты прямо сейчас. Скорее всего, хотя бы один xmlrpc.php открыт.

Leave your thought here

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Поддержать автора

Если вам понравилась статья — вы можете поддержать автора.

Crypto donation button by NOWPayments