Блог

wlwmanifest.xml и RSD: два маяка, которые говорят ботам «здесь WordPress».

wlwmanifest
Linux / Wordpress

wlwmanifest.xml и RSD: два маяка, которые говорят ботам «здесь WordPress».

Ты закрыл xmlrpc.php в nginx. Поставил fail2ban. Настроил Cloudflare WAF. Доволен собой.

Открываешь access.log через неделю — и видишь:

94.102.49.190 - - [26/May/2026:04:17:33 +0300] "GET //wp-includes/wlwmanifest.xml HTTP/1.1" 404 162
94.102.49.191 - - [26/May/2026:04:17:34 +0300] "GET //xmlrpc.php?rsd HTTP/1.1" 404 162
94.102.49.192 - - [26/May/2026:04:17:35 +0300] "GET //blog/wp-includes/wlwmanifest.xml HTTP/1.1" 404 162
94.102.49.193 - - [26/May/2026:04:17:35 +0300] "GET //web/wp-includes/wlwmanifest.xml HTTP/1.1" 404 162

404 — значит файлы не нашли. Казалось бы, всё хорошо. Но боты не ищут эти файлы чтобы их читать. Они ищут их чтобы понять: тут WordPress? Если да — следующий запрос будет к xmlrpc.php или wp-login.php.

Это разведка. И большинство администраторов её не закрывают.

ЧТО ЭТО ТАКОЕ И ОТКУДА ВЗЯЛОСЬ

wlwmanifest.xml — XML-файл из эпохи Windows Live Writer. Microsoft выпустила этот блог-редактор в середине 2000-х, WordPress добавил поддержку начиная с версии 2.3.1. Файл описывал возможности сайта для десктопного клиента: какие форматы изображений поддерживаются, как публиковать посты, как подключиться к xmlrpc.php.

Microsoft окончательно свернула Windows Live Writer в 2017 году. WordPress удалил wlwmanifest.xml из ядра в версии 6.3.0 (август 2023) — функция wlwmanifest_link() помечена как deprecated и больше ничего не выводит. Но:

Во-первых, огромное количество сайтов до сих пор работает на WordPress младше 6.3. Во-вторых, сайты которые обновились с более старой версии без полной переустановки — могут иметь файл wlwmanifest.xml физически лежащим в /wp-includes/ даже при WordPress 6.3+. В-третьих, боты не проверяют версию WordPress перед сканированием — они просто стучат по всем известным путям.

Проверить есть ли файл на твоём сервере:

find /var/www -name "wlwmanifest.xml" 2>/dev/null

RSD (Really Simple Discovery) — ещё более старый стандарт, 2002 год. Позволял внешним приложениям автоматически обнаруживать API блог-платформы. WordPress до сих пор добавляет ссылку на него в <head> каждой страницы через функцию rsd_link(), которая не депрекирована:

<link rel="EditURI" type="application/rsd+xml" title="RSD"
      href="https://example.com/xmlrpc.php?rsd" />

Обрати внимание на значение href: xmlrpc.php?rsd. RSD-ссылка буквально указывает на xmlrpc.php. Любой бот, прочитавший HTML страницы, сразу знает где искать.

ПОЧЕМУ ЭТО ПРОБЛЕМА

Три уровня раскрытия информации:

Уровень 1: подтверждение что это WordPress

type="application/wlwmanifest+xml" и путь /wp-includes/wlwmanifest.xml — уникальный идентификатор WordPress. Ни один другой CMS не использует этот MIME-тип и этот путь. Боты-сканеры используют именно его для детектирования WordPress, даже если файл возвращает 404.

Уровень 2: прямое указание на xmlrpc.php

RSD-ссылка в &lt;head&gt; буквально содержит URL xmlrpc.php. Можно закрыть xmlrpc.php в nginx — но если RSD-ссылка в HTML, сканер знает что xmlrpc.php существует и будет пробовать его напрямую.

Уровень 3: паттерн сканирования

Типичная последовательность бот-атаки на WordPress выглядит так:

GET /wp-includes/wlwmanifest.xml  → 200 или 404? Есть WordPress?
GET /xmlrpc.php?rsd               → подтверждение xmlrpc.php
GET /xmlrpc.php                   → атака начинается
POST /xmlrpc.php                  → брутфорс / pingback / DDoS

Закрыв xmlrpc.php но оставив wlwmanifest.xml и RSD, ты убрал саму атаку, но оставил разведку. Боты это видят, делают заметки и возвращаются позже — может быть с другого IP, может быть с другим вектором (wp-login.php, REST API).

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

Уровень 1: nginx — блокировать прямой доступ

Это работает независимо от версии WordPress и независимо от того, лежит файл на диске или нет. Запрос блокируется до PHP.

Добавить в блок server{} твоего nginx-конфига:

# Блокировать wlwmanifest.xml — работает для всех версий WordPress
location ~* /wp-includes/wlwmanifest\.xml {
    deny all;
    access_log off;
    log_not_found off;
}

# Блокировать RSD endpoint
# Если xmlrpc.php уже закрыт через location = /xmlrpc.php { deny all; }
# то /xmlrpc.php?rsd тоже закрыт — nginx exact match игнорирует query string.
# Отдельный блок нужен только если xmlrpc.php закрыт через regex location ~*
location ~* /xmlrpc\.php {
    deny all;
    access_log off;
    log_not_found off;
}

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

nginx -t && systemctl reload nginx

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

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

Уровень 2: WordPress — убрать RSD-ссылку из HTML <head>

Блокировка в nginx закрывает прямой доступ к endpoint, но RSD-ссылка всё ещё присутствует в HTML каждой страницы. Бот читает HTML и видит xmlrpc.php?rsd в href. Нужно убрать её из источника.

rsd_link() — актуальная функция, не депрекирована. Убрать через mu-plugin:

# Создать mu-plugin — загружается всегда, не требует активации
cat > /var/www/html/wp-content/mu-plugins/remove-legacy-links.php << 'EOF'
<?php
/**
 * Plugin Name: Remove Legacy Discovery Links
 * Description: Removes RSD link and WordPress version from <head>
 */

// RSD-ссылка (актуальна для всех версий WordPress)
remove_action('wp_head', 'rsd_link');

// Версия WordPress в <meta name="generator"> — раскрывает версию для CVE-поиска
remove_action('wp_head', 'wp_generator');

// wlwmanifest — нужно только для WordPress до 6.3
// В 6.3+ функция депрекирована и не выводит ничего, строка безвредна
remove_action('wp_head', 'wlwmanifest_link');
EOF

mu-plugins загружаются автоматически, не требуют активации, не отображаются в списке плагинов и не отключаются случайно.

Проверить результат:

curl -s https://example.com/ | grep -E "wlwmanifest|EditURI|generator.*WordPress"
# Всё должно быть пустым

ПРОВЕРИТЬ ВЕРСИЮ WORDPRESS И НАЛИЧИЕ ФАЙЛА

Понять с чем именно имеешь дело:

# Версия WordPress
cat /var/www/html/wp-includes/version.php | grep wp_version

# Есть ли файл wlwmanifest.xml физически на диске
find /var/www -name "wlwmanifest.xml" 2>/dev/null

# Доступен ли файл по HTTP
curl -o /dev/null -w "%{http_code}" https://example.com/wp-includes/wlwmanifest.xml

Если WordPress 6.3+ и файл не найден командой find — wlwmanifest.xml не существует. Но nginx-блокировка всё равно нужна: боты стучат по этому пути на всех сайтах подряд, и 403 вместо 404 позволяет их банить через fail2ban.

ДИАГНОСТИКА: СКОЛЬКО ЗАПРОСОВ ПРИХОДИТ

Посмотреть активность сканеров за последние сутки:

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

Посмотреть паттерн — одни IP часто запрашивают wlwmanifest, xmlrpc и wp-login в связке:

grep -E "wlwmanifest|xmlrpc|wp-login" /var/log/nginx/access.log | \
  awk '{print $1, $7}' | sort | head -40

Добавить wlwmanifest в fail2ban фильтр для WordPress:

# /etc/fail2ban/filter.d/nginx-wordpress.conf
[Definition]
failregex = ^<HOST> - - \[.*\] "(GET|POST) .*xmlrpc\.php.* HTTP/.*" (200|403|404) \d+
            ^<HOST> - - \[.*\] "GET .*wlwmanifest\.xml HTTP/.*" (200|403|404) \d+
ignoreregex =

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

Ошибка 1: «У меня WordPress 6.3+ — wlwmanifest меня не касается»

Касается — с двух сторон. Первое: боты не проверяют твою версию WordPress, они стучат по /wp-includes/wlwmanifest.xml на всех сайтах подряд. Без nginx-блокировки они получают 404 и фиксируют что тут WordPress. Второе: после обновления с более старой версии файл может физически лежать на диске.

Ошибка 2: «Файл отдаёт 404 — значит всё хорошо»

404 означает что бот получил ответ от сервера, записал что сканировал этот адрес, и сделал вывод — тут WordPress (потому что только WordPress запрашивают именно этот путь). 403 лучше: можно забанить того кто спрашивает.

Ошибка 3: «Убрал RSD через functions.php родительской темы»

Изменения в functions.php родительской темы не переживут обновление темы. Используй mu-plugin или functions.php дочерней темы.

ПОЛНЫЙ ЧЕКЛИСТ ПРОВЕРКИ

# 1. wlwmanifest.xml недоступен напрямую
curl -o /dev/null -w "%{http_code}" https://example.com/wp-includes/wlwmanifest.xml
# Ожидаем: 403

# 2. RSD endpoint недоступен
curl -o /dev/null -w "%{http_code}" "https://example.com/xmlrpc.php?rsd"
# Ожидаем: 403

# 3. RSD-ссылка не присутствует в HTML
curl -s https://example.com/ | grep -c "EditURI"
# Ожидаем: 0

# 4. Версия WordPress не видна в HTML
curl -s https://example.com/ | grep "generator.*WordPress"
# Ожидаем: пустой вывод

ИТОГ

wlwmanifest.xml и RSD — не уязвимости в классическом смысле. Они не дают атакующему прямого доступа. Но они дают информацию: тут WordPress, тут xmlrpc.php, вот точный URL.

В контексте автоматизированных атак информация = преимущество. Боты работают по спискам сигнатур. Убрав эти сигнатуры, ты выпадаешь из большинства автоматических сканов.

Два действия, пять минут:

# nginx: закрыть прямой доступ
location ~* /wp-includes/wlwmanifest\.xml {
    deny all;
    access_log off;
    log_not_found off;
}

# WordPress mu-plugin: убрать RSD и версию из HTML
remove_action('wp_head', 'rsd_link');
remove_action('wp_head', 'wp_generator');

Это не серебряная пуля. Это закрытие лишних дверей которые всё равно никому не нужны.

Leave your thought here

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

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

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

Crypto donation button by NOWPayments