Безопасность сайта

XML-RPC в WordPress: зачем его закрывают и когда это ломает сайт

Что такое xmlrpc.php, как через него перебирают пароли и усиливают атаки, как закрыть и что перестанет работать после отключения.

14 минут чтения 4 Дмитрий Акиндинов
Содержание
  1. Что такое XML-RPC и откуда он взялся
  2. Перебор паролей пачками: почему это быстрее обычного брутфорса
  3. Pingback: чужие атаки вашими руками
  4. Как понять, что долбят именно туда
  5. Четыре способа закрыть
  6. Что сломается после отключения
  7. Оставить, но ограничить
  8. Проверка и что мониторить дальше
  9. Что сделать прямо сейчас
  10. Частые вопросы

Через служебный файл удалённого доступа пароли перебирают пачками: за один запрос можно проверить сотни комбинаций, и ограничение попыток на форме входа это не ловит. Если протокол не используется, его закрывают. Работы на 20-30 минут при наличии доступа к файлам.

В логах сайта одна и та же строка сотнями: POST /xmlrpc.php HTTP/1.1" 200. Адреса разные, интервал секунда, хостинг шлёт письмо про превышение лимита по CPU, админка открывается через раз. Это не персональная травля, это фоновый шум интернета: боты перебирают пароли через файл xmlrpc.php, который лежит в корне почти каждого сайта на WordPress. Разберу, откуда взялся этот файл, почему через него перебор идёт в сотни раз быстрее обычного, какими четырьмя способами его закрыть и что после этого перестанет работать.

Файл xmlrpc.php под градом запросов и щит перед ним

За шесть лет абонентки я закрывал xmlrpc.php больше чем на сотне сайтов и ровно дважды открывал обратно. Оба раза из-за Jetpack. Так что мой ответ на вопрос «закрывать или нет» вы примерно угадали, но интереснее разобраться в деталях: «закрыть» тоже можно неправильно, и я видел сайты, где заглушка стояла, а перебор продолжался.

Что такое XML-RPC и откуда он взялся

XML-RPC это протокол удалённых вызовов, придуманный в 1998 году. Клиент шлёт на сервер POST-запрос с XML внутри, в котором написано имя метода и параметры, сервер отвечает таким же XML. Всё. Никакой авторизации по токенам, логин и пароль лежат прямо в теле запроса открытым текстом (шифрует только HTTPS сверху).

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

До версии 3.5 (декабрь 2012) протокол был выключен по умолчанию, а в настройках стояла галочка. В 3.5 галочку убрали и включили XML-RPC насовсем: разработчики решили, что мобильное приложение должно работать из коробки. С тех пор файл открыт у всех, кто его специально не закрыл, и отдаёт наружу примерно 70 методов.

Посмотреть список можно одним запросом, никакой авторизации для этого не нужно:

curl -s -X POST https://example.ru/xmlrpc.php 
  -H 'Content-Type: text/xml' 
  -d '<?xml version="1.0"?><methodCall><methodName>system.listMethods</methodName><params></params></methodCall>'

В ответе будут wp.getUsersBlogs, wp.newPost, metaWeblog.newPost, pingback.ping и компания. Ботам интересны ровно два из них, и оба вы, скорее всего, никогда в жизни не использовали.

Сегодня всё то же самое умеет REST API (/wp-json/), который живёт в ядре с версии 4.7 и работает по нормальным правилам: заголовок авторизации, пароли приложений, отзыв доступа в один клик. XML-RPC остался в ядре ради совместимости, и уже несколько лет идут разговоры о его выносе в отдельный плагин. Пока не вынесли.

Перебор паролей пачками: почему это быстрее обычного брутфорса

Обычный перебор через форму входа выглядит так: POST на /wp-login.php, одна попытка на запрос. Любой плагин лимитов ловит это за пять минут, да и хостинг начинает резать по количеству запросов. Тысяча попыток это тысяча запросов, шумно и медленно.

Метод wp.getUsersBlogs в XML-RPC принимает логин и пароль и отвечает списком сайтов пользователя. Уже удобнее для бота, но всё ещё один запрос на одну попытку. Дальше начинается интересное: в протоколе есть служебный метод system.multicall, который позволяет упаковать в один запрос сразу пачку вызовов. Изначально это было сделано ради экономии на медленных каналах.

Выглядит атака так:

<?xml version="1.0"?>
<methodCall>
  <methodName>system.multicall</methodName>
  <params><param><value><array><data>
    <value><struct>
      <member><name>methodName</name><value><string>wp.getUsersBlogs</string></value></member>
      <member><name>params</name><value><array><data>
        <value><array><data>
          <value><string>admin</string></value>
          <value><string>Passw0rd1</string></value>
        </data></array></value>
      </data></array></value></member>
    </struct></value>
    <!-- и так ещё 500 раз с другими паролями -->
  </data></array></value></param></params>
</methodCall>

Один HTTP-запрос, пятьсот проверенных паролей. Для системы лимитов на уровне веб-сервера это единичное обращение, для плагина ограничения попыток входа тоже часто ноль (если он вешается только на форму wp-login.php). Отсюда и берётся разница: то, что через форму заняло бы неделю, через system.multicall укладывается в часы.

В WordPress 4.4 прикрутили частичное лечение: после первой неудачной авторизации внутри мультивызова остальные вызовы в том же запросе отбиваются ошибкой. Это сильно испортило ботам жизнь, но не убило схему. Во-первых, никуда не делся обычный wp.getUsersBlogs по одной попытке на запрос, и он всё ещё дешевле формы входа. Во-вторых, сам разбор гигантского XML жрёт память и процессорное время: сайт кладут не подбором, а самой нагрузкой от парсинга.

На shared-хостинге это выглядит как загадочные тормоза без видимой причины. Смотришь панель: CPU в потолке, лимит по процессам выбран, PHP-воркеры заняты. Смотришь логи: POST /xmlrpc.php, десять тысяч строк за ночь. При исчерпании лимита по процессам хостинг просто перестаёт запускать PHP, и посетитель получает 503 вместо страницы.

Отдельно замечу: закрытие xmlrpc.php не отменяет базовую гигиену. Логин admin, пароль из словаря и отсутствие ограничения попыток входа делают форму wp-login.php следующей мишенью в тот же день.

Pingback: чужие атаки вашими руками

Второй метод, ради которого ботам нужен ваш сайт, это pingback.ping. Задумка была мирная: вы упомянули чужую статью, ваш WordPress стучится к соседу, сосед приходит проверить ссылку и ставит у себя обратный комментарий. Механика из эпохи блогов, когда так строили ссылочный обмен.

Проблема в том, что стучаться можно от чужого имени. Злоумышленник отправляет вашему сайту pingback.ping, где в качестве адреса для проверки указывает жертву. Ваш сервер послушно идёт по этому адресу и делает запрос. Теперь возьмите пять тысяч сайтов на WordPress с открытым xmlrpc.php, разошлите им такой запрос, и жертва получает распределённую атаку от пяти тысяч уважаемых доменов, а не от подозрительных адресов из даркнета. Классическая схема усиления, которую массово эксплуатировали с 2014 года и продолжают эксплуатировать сейчас, потому что открытых сайтов меньше не стало.

Для вас это выглядит как непонятный исходящий трафик, жалобы хостера и иногда блокировка аккаунта за участие в атаке. Ваш сайт при этом технически не взломан. Просто им пользуются.

Второй способ применения того же метода это разведка внутренней сети: подсовывая адреса вида http://127.0.0.1:3306/, атакующий по времени ответа и тексту ошибки понимает, какие сервисы крутятся на сервере. В ядре давно есть фильтры против обращения на локальные адреса, но обход находят регулярно.

Если сомневаетесь, участвует ли ваш сайт в чужих атаках, ищите в логах входящие pingback.ping и одновременно смотрите исходящие соединения. Общая методика разбора логов, включая склейку событий по времени, разобрана в статье про то, как найти точку взлома по логам.

Как понять, что долбят именно туда

Прежде чем что-то закрывать, полезно убедиться, что проблема действительно здесь. Логи Apache или nginx на большинстве хостингов лежат в ~/logs/ или /var/log/nginx/, в панели Timeweb и Beget они выгружаются кнопкой.

Первое, что смотрю:

# сколько всего обращений к файлу за сутки
grep -c 'xmlrpc.php' access.log

# топ адресов, которые его дёргают
awk '$7 ~ /xmlrpc.php/ {print $1}' access.log | sort | uniq -c | sort -rn | head -20

# распределение по часам, чтобы увидеть волну
grep 'xmlrpc.php' access.log | cut -d: -f2 | sort | uniq -c

# размер тела запроса: гигантские POST это multicall
awk '$7 ~ /xmlrpc.php/ {print $10}' access.log | sort -rn | head -5

Как читать результат:

Что видитеЧто это значит
10-50 запросов в сутки с одного адресафоновое сканирование, реагировать не срочно
500 и больше в сутки, адреса разныевас перебирают ботнетом, закрывать сегодня
Ответы 200 и тело ответа в десятки килобайтmulticall отработал, сервер честно парсил пачку паролей
В логе есть pingback.pingваш сайт используют как усилитель для чужой атаки
Код 403 или 405 в ответфайл уже закрыт, боты просто ещё не в курсе

Отдельно проверьте, не появились ли новые пользователи с ролью администратора и не менялись ли файлы темы. Перебор часто идёт вслед за успешным входом, а не вместо него: если в wp_users завёлся лишний аккаунт с ролью administrator, закрытием файла дело уже не обойдётся.

Четыре способа закрыть

Способы отличаются тем, на каком слое отсекается запрос. Чем раньше, тем дешевле для сервера: заглушка на уровне веб-сервера стоит доли миллисекунды, а фильтр в PHP означает, что WordPress уже загрузился целиком.

.htaccess для Apache и LiteSpeed

Самый частый вариант на shared-хостинге. В .htaccess в корне сайта, до блока # BEGIN WordPress:

<Files "xmlrpc.php">
  <IfModule mod_authz_core.c>
    Require all denied
  </IfModule>
  <IfModule !mod_authz_core.c>
    Order deny,allow
    Deny from all
  </IfModule>
</Files>

Двойная конструкция нужна потому, что на Apache 2.2 работает старый синтаксис, на 2.4 новый, а какой у хостера, вы часто не знаете до первой ошибки 500. Другие полезные правила для этого же файла, от блокировки парсеров до защиты wp-config.php, разбирал в статье про htaccess против ботов и парсеров.

Важная деталь: если сайт стоит за nginx, который отдаёт статику и проксирует PHP в Apache, .htaccess всё равно сработает, потому что до PHP запрос доходит через Apache. А вот на чистом nginx с php-fpm файл .htaccess не читается вообще и висит там мёртвым грузом. Проверить легко: curl -sI https://example.ru/xmlrpc.php после правки должен отдать 403.

nginx

Если у вас VPS с nginx, правило пишется в конфиг сайта, обычно в /etc/nginx/sites-available/example.ru:

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

return 444 это специальный код nginx: соединение закрывается без ответа вообще. Ботам не достаётся даже страницы ошибки, а лог не пухнет. Если вам нужна честная 403 для внешних проверок и сканеров, поменяйте на return 403;. После правки обязательно nginx -t && systemctl reload nginx, иначе синтаксическая ошибка положит все сайты на сервере.

Фильтры WordPress через mu-plugin

Вариант для тех, у кого нет доступа к конфигам, или для дополнительного слоя. Кладём файл wp-content/mu-plugins/disable-xmlrpc.php:

<?php
// Отключить методы, требующие авторизации
add_filter( 'xmlrpc_enabled', '__return_false' );

// Убрать pingback из списка методов
add_filter( 'xmlrpc_methods', function ( $methods ) {
    unset( $methods['pingback.ping'], $methods['pingback.extensions.getPingbacks'] );
    return $methods;
} );

// Не рассказывать всему миру, что pingback тут есть
add_filter( 'wp_headers', function ( $headers ) {
    unset( $headers['X-Pingback'] );
    return $headers;
} );

Именно так, тремя фильтрами. Одинокий add_filter('xmlrpc_enabled', '__return_false'), который советуют в половине статей, отключает только методы с авторизацией. Pingback после него продолжает работать, потому что авторизации не требует, и ваш сайт остаётся усилителем для чужих атак. Это самая частая ошибка при «отключении xmlrpc»: галочка поставлена, дыра открыта.

Папку mu-plugins может потребоваться создать, она подхватывается автоматически и не отключается из админки. Файлам ставим права 644, папке 755, как и всему остальному.

Плагины

Wordfence, iThemes Security, Disable XML-RPC Pingback, All In One WP Security. Все умеют выключать XML-RPC галочкой в настройках. Работает, но с оговоркой: запрос всё равно доходит до PHP, WordPress загружается, плагин отрабатывает. При ковровой бомбардировке в тысячи запросов в минуту вы всё равно выбираете лимит хостинга по процессам, просто пароли не подберут.

Сводка по слоям:

СпособГде отсекаетсяНагрузка на PHPКому подходит
.htaccessApache/LiteSpeedнетshared-хостинг, большинство клиентов
nginx locationвеб-сервернетVPS, выделенный сервер
mu-pluginPHP после загрузки ядраестьнет доступа к конфигам, второй слой
Плагин безопасностиPHP после загрузки ядра и плагиновесть, максимальнаяесли плагин уже стоит ради другого

На боевых проектах я обычно ставлю два слоя сразу: правило на веб-сервере плюс mu-plugin. Первый снимает нагрузку, второй страхует от переезда на другой хостинг, где конфиг не поедет вместе с файлами. За перенос правил при смене площадки, кстати, отвечает подрядчик, и это первое, что отваливается при кустарном переезде.

Что сломается после отключения

Честная часть разговора. XML-RPC не бесполезный мусор, у него есть живые потребители, и их надо проверить до того, как закрывать.

Что использует XML-RPCСломается лиЧто делать
Приложение WordPress для iOS и Androidнет, если WP свежийприложение с 5.6 умеет REST API и пароли приложений
Jetpackда, полностьюсвязь с WordPress.com идёт только через xmlrpc.php
Pingback и trackback между сайтамидав 2026 это почти всегда спам, отключается сознательно
Публикация из внешних редакторов (Open Live Writer, старые CMS-агрегаторы)дапереводить на REST API или публиковать вручную
Старые интеграции Zapier, IFTTT, самописные постилкизависит от коннекторасмотреть документацию, часто есть REST-режим
Плагины миграции и бэкапа (некоторые сборки)иногдапроверить после отключения, у большинства свой канал
Rank Math, Yoast, WooCommerce, формынетработают через REST и админку

Практика: на девяти сайтах из десяти в малом бизнесе не используется ничего из левой колонки. Владелец правит тексты из админки в браузере, картинки грузит оттуда же, приложение WordPress не установлено даже в теории. Отключение проходит незаметно.

Оставшийся десятый случай это Jetpack. Он подключается к WordPress.com именно через xmlrpc.php, и при закрытом файле связь отваливается с ошибкой «site is inaccessible». Если Jetpack нужен ради статистики или защиты форм, придётся либо оставлять доступ по списку адресов (об этом ниже), либо искать замену. Для российского малого бизнеса замена обычно проще: аналитику закрывает Яндекс Метрика, а спам в формах ловится honeypot-полем без внешних сервисов.

Отдельно про пингбеки. Если вы их специально не выключали, у вас в комментариях уже висит мусор от сайтов-агрегаторов. Проверьте в настройках обсуждения галочку «Разрешить оповещения с других блогов» и снимите её. На старые записи она не распространяется, для них нужен SQL:

UPDATE wp_posts SET ping_status = 'closed' WHERE post_type IN ('post','page');

Перед любым запросом к базе делаем бэкап, префикс wp_ меняем на свой из wp-config.php.

Оставить, но ограничить

Бывает, что закрыть насовсем нельзя: сидит на xmlrpc.php рабочая интеграция или тот же Jetpack. Тогда работает компромисс: доступ есть, но не для всех и не с любой частотой.

Доступ по списку адресов в .htaccess:

<Files "xmlrpc.php">
  Require all denied
  Require ip 192.0.2.10
  Require ip 198.51.100.0/24
</Files>

Адреса Jetpack публикуются на сайте Automattic и периодически меняются, так что раз в полгода список надо сверять. Для самописной интеграции с фиксированного сервера всё проще: один адрес, один раз.

Ограничение частоты на nginx:

limit_req_zone $binary_remote_addr zone=xmlrpc:10m rate=10r/m;

location = /xmlrpc.php {
    limit_req zone=xmlrpc burst=5 nodelay;
    limit_req_status 429;
    include fastcgi_params;
    fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

Десять запросов в минуту с адреса хватает любой легальной интеграции и убивает перебор как явление. Директива limit_req_zone ставится в блок http, а не внутрь server, иначе nginx не запустится.

На shared-хостинге лимитов такого рода нет, там остаётся список разрешённых адресов. Если хостер даёт панель с базовым WAF, правило по URI тоже сгодится, но проверяйте, что оно реально срабатывает, а не просто существует в интерфейсе.

Проверка и что мониторить дальше

Отключили. Теперь убеждаемся, что отключили, а не поверили в отключение. Три команды:

# 1. GET должен вернуть 403, 444 или 405, но не 200
curl -sI https://example.ru/xmlrpc.php | head -1

# 2. POST с методом listMethods не должен отдавать список методов
curl -s -X POST https://example.ru/xmlrpc.php -H 'Content-Type: text/xml' 
  -d '<?xml version="1.0"?><methodCall><methodName>system.listMethods</methodName><params></params></methodCall>' | head -c 300

# 3. Заголовка X-Pingback в ответе главной быть не должно
curl -sI https://example.ru/ | grep -i pingback

Если на первый запрос приходит 405 Method Not Allowed с текстом «XML-RPC server accepts POST requests only», файл живой и открытый: это стандартный ответ WordPress на GET. Многих это сбивает, потому что код не 200 и кажется, что всё закрыто. Проверяйте вторым запросом.

Чек-лист после закрытия:

  • зайти в админку и опубликовать тестовую запись, убедиться, что публикация работает;
  • открыть 3-4 страницы сайта и форму заявки, проверить отправку;
  • если стоял Jetpack, посмотреть его статус в админке;
  • через сутки посмотреть логи: обращения к xmlrpc.php должны отдавать 403 или 444;
  • через неделю сверить нагрузку в панели хостинга с прошлой неделей.

Что мониторить дальше. Боты не перестают стучаться сразу, ещё месяц-два вы будете видеть их в логах, просто теперь дёшево. Смотреть стоит на смещение атаки: когда xmlrpc.php закрыт, следующая цель это /wp-login.php и REST-эндпоинт /wp-json/wp/v2/users, который по умолчанию отдаёт список логинов авторов. Второй закрывается фильтром или плагином, и это стоит сделать в тот же заход: перебор без знания логина втрое бессмысленнее.

Из практики: связка «xmlrpc.php закрыт на веб-сервере, лимит попыток входа на форме, двухфакторная аутентификация в админке хотя бы для администраторов» снимает процентов девяносто автоматических атак на типовой сайт малого бизнеса. Оставшиеся десять это целевые истории, там нужен другой разговор и другой бюджет.

Что сделать прямо сейчас

Короткий порядок действий, если вы дочитали и хотите закрыть вопрос сегодня.

  1. Посмотрите логи за последние сутки командой grep -c 'xmlrpc.php' access.log. Цифра больше сотни это повод не откладывать.
  2. Сделайте бэкап файлов и базы. Правка .htaccess с ошибкой роняет сайт в 500, откат должен быть под рукой.
  3. Проверьте, стоит ли Jetpack и есть ли внешние публикаторы. Если нет, закрывайте полностью.
  4. Добавьте правило на веб-сервере плюс mu-plugin с тремя фильтрами.
  5. Проверьте тремя curl-командами выше и опубликуйте тестовую запись.

Работы тут на 20-30 минут, если есть доступ к файлам и логам. Часа полтора, если доступ ищут по переписке с бывшим подрядчиком, а логи хостинг отдаёт только за трое суток. Лечение уже взломанного сайта стоит от 30 000 рублей и занимает несколько дней, так что арифметика очевидная.

Если разбираться самому некогда, я делаю такую профилактику в рамках абонентки: закрытие xmlrpc.php, лимиты на вход, разбор логов, обновления ядра и плагинов по расписанию. Цены и состав пакетов есть в тарифах, разовую проверку сайта тоже делаю. Когда сайт уже лежит или в логах видно активный перебор, пишите сразу через форму, такие задачи беру вне очереди. Остальные материалы по защите сайта собраны в рубрике про безопасность.

Частые вопросы

Что такое XML-RPC и зачем его закрывают

Это протокол удалённого доступа к сайту. Через него подбирают пароли пачками и организуют атаки чужими руками. Большинству сайтов он не нужен вовсе.

Как понять, что долбят именно туда

По логу доступа: массовые запросы к этому файлу с разных адресов. Косвенный признак это письмо хостинга о превышении лимитов при неизменившемся трафике.

Что сломается после отключения

Мобильное приложение WordPress, публикация через сторонние редакторы и некоторые старые плагины. Проверьте, пользуетесь ли вы этим, прежде чем закрывать.

Как закрыть

Правилом на уровне веб-сервера, чтобы запрос не доходил до PHP. Отключение фильтром внутри CMS работает хуже: сервер всё равно тратит ресурсы на запуск.

Передайте техническую часть в надёжные руки.

Слежу за хостингом, бэкапами и обновлениями. Когда что-то пойдёт не так – звоните мне, не своему программисту, не в спортзал и не маме.

Короткие вопросы по теме

Все вопросы по теме "Безопасность сайта"

Заявка

Обсудим задачу

Напишите имя и телефон, выберите удобный канал. Перезвоню или напишу в течение часа в рабочее время.

Куда удобнее ответить?