Tinklaraštis

wlwmanifest.xml ir RSD: du švyturiai, kurie botams sako „čia WordPress”.

wlwmanifest
Linux / Wordpress

wlwmanifest.xml ir RSD: du švyturiai, kurie botams sako „čia WordPress”.

Užblokavai xmlrpc.php nginx konfigūracijoje. Nustatei fail2ban. Sukonfigūravai Cloudflare WAF. Jautiesi gerai.

Atidaro access.log po savaitės — ir matai štai tai:

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 — failai nerasti. Atrodo viskas gerai. Bet botai neprašo šių failų juos skaityti. Jie prašo jų tam, kad atsakytų į vieną klausimą: ar čia WordPress? Jei taip — kitas užklausa bus į xmlrpc.php arba wp-login.php.

Tai žvalgyba. Ir dauguma administratorių jos neuždarė.

KAS TAI YRA IR IŠ KUR ATSIRADO

wlwmanifest.xml — XML failas iš Windows Live Writer eros. Microsoft išleido šį staliniojo kompiuterio tinklaraščių redaktorių 2000-ųjų viduryje; WordPress pridėjo palaikymą nuo 2.3.1 versijos. Failas aprašė svetainės galimybes staliniojo kompiuterio klientui: palaikomi vaizdo formatai, kaip skelbti įrašus, kaip prisijungti prie xmlrpc.php.

Microsoft galutinai nutraukė Windows Live Writer 2017 metais. WordPress pašalino wlwmanifest.xml iš branduolio 6.3.0 versijoje (2023 m. rugpjūtis) — funkcija wlwmanifest_link() dabar yra pasenusi (deprecated) ir nieko neišveda. Tačiau:

Pirma, didelis skaičius svetainių vis dar veikia su WordPress senesnėmis nei 6.3 versijomis. Antra, svetainės, kurios atnaujintos iš senesnės versijos be visiško diegimo iš naujo, gali turėti failą wlwmanifest.xml fiziškai /wp-includes/ aplanke net su WordPress 6.3+. Trečia, botai netikrina tavo WordPress versijos prieš skenuodami — jie tiesiog beldžiasi į visus žinomus kelius.

Patikrinti, ar failas egzistuoja serveryje:

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

RSD (Really Simple Discovery) — dar senesnis standartas iš 2002 metų. Leido išorinėms programoms automatiškai aptikti tinklaraščių platformos API. WordPress vis dar prideda nuorodą į jį kiekvieno puslapio <head> skyriuje per funkciją rsd_link(), kuri nėra pasenusi:

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

Atkreipk dėmesį į href reikšmę: xmlrpc.php?rsd. RSD nuoroda tiesiogiai nurodo į xmlrpc.php. Bet kuris botas, perskaitęs puslapio HTML, iš karto žino kur jo ieškoti.

KODĖL TAI PROBLEMA

Trys informacijos atskleidimo lygiai:

1 lygis: patvirtinimas, kad tai WordPress

type="application/wlwmanifest+xml" ir kelias /wp-includes/wlwmanifest.xml yra unikalus WordPress identifikatorius. Jokia kita TVS nenaudoja šio MIME tipo ar šio kelio. Botų skeneriai naudoja jį konkrečiai WordPress diegimams aptikti — net neskaitydami paties failo ir nepriklausomai nuo to, ar grąžinamas 200 ar 404.

2 lygis: tiesioginė nuoroda į xmlrpc.php

RSD nuoroda &lt;head&gt; skyriuje tiesiogiai turi xmlrpc.php URL. Gali užblokuoti xmlrpc.php nginx konfigūracijoje — bet jei HTML yra RSD nuoroda, skeneris žino, kad xmlrpc.php egzistuoja ir bandys jį tiesiogiai.

3 lygis: skenavimo šablonas

Tipinė boto atakos prieš WordPress seka atrodo taip:

GET /wp-includes/wlwmanifest.xml  → 200 ar 404? Ar čia WordPress?
GET /xmlrpc.php?rsd               → patvirtinimas, kad xmlrpc.php egzistuoja
GET /xmlrpc.php                   → ataka prasideda
POST /xmlrpc.php                  → brutalios jėgos / pingback / DDoS

Užblokavus xmlrpc.php, bet palikus wlwmanifest.xml ir RSD, sustabdei pačią ataką, bet palikai žvalgybą atvirą. Botai tai mato, daro pastabas ir grįžta vėliau — gal iš kito IP, gal su kitu vektoriumi (wp-login.php, REST API).

KAIP UŽDARYTI: DU LYGIAI

1 lygis: nginx — blokuoti tiesioginę prieigą

Tai veikia nepriklausomai nuo WordPress versijos ir nepriklausomai nuo to, ar failas egzistuoja diske. Užklausa blokuojama prieš pasiekiant PHP.

Pridėti į nginx konfigūracijos server{} bloką:

# Blokuoti wlwmanifest.xml — veikia visoms WordPress versijoms
location ~* /wp-includes/wlwmanifest\.xml {
    deny all;
    access_log off;
    log_not_found off;
}

# Blokuoti RSD endpoint
# Jei xmlrpc.php jau užblokuotas per location = /xmlrpc.php { deny all; }
# tada /xmlrpc.php?rsd jau yra aprėptas — nginx tikslus atitikimas ignoruoja query string.
# Atskiras blokas reikalingas tik jei xmlrpc.php užblokuotas per regex location ~*
location ~* /xmlrpc\.php {
    deny all;
    access_log off;
    log_not_found off;
}

Patikrinti, ar blokavimas veikia:

nginx -t && systemctl reload nginx

curl -I https://example.com/wp-includes/wlwmanifest.xml
# Laukiamas atsakymas: HTTP/1.1 403 Forbidden

curl -I "https://example.com/xmlrpc.php?rsd"
# Laukiamas atsakymas: HTTP/1.1 403 Forbidden

2 lygis: WordPress — pašalinti RSD nuorodą iš HTML <head>

Blokavimas nginx lygmeniu stabdo tiesioginę prieigą prie endpoint, tačiau RSD nuoroda vis dar yra kiekvieno puslapio HTML. Botas skaito HTML ir mato xmlrpc.php?rsd href reikšmėje. Nuorodą reikia pašalinti iš šaltinio.

rsd_link() yra aktuali funkcija, ne pasenusi. Pašalinti per mu-plugin:

# Sukurti mu-plugin — įkeliamas automatiškai, nereikia aktyvavimo
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 nuoroda (aktuali visoms WordPress versijoms)
remove_action('wp_head', 'rsd_link');

// WordPress versija <meta name="generator"> — atskleidžia versiją CVE paieškai
remove_action('wp_head', 'wp_generator');

// wlwmanifest — reikalinga tik WordPress versijai senesnei nei 6.3
// 6.3+ funkcija yra pasenusi ir nieko neišveda, eilutė nekenksminga
remove_action('wp_head', 'wlwmanifest_link');
EOF

mu-plugins įkeliami automatiškai, nereikia aktyvavimo, nerodomi įprastame papildinių sąraše ir negali būti atsitiktinai išjungti.

Patikrinti rezultatą:

curl -s https://example.com/ | grep -E "wlwmanifest|EditURI|generator.*WordPress"
# Viskas turėtų būti tuščia

PATIKRINTI WORDPRESS VERSIJĄ IR FAILO BUVIMĄ

Suprasti su kuo konkrečiai susiduriama:

# WordPress versija
cat /var/www/html/wp-includes/version.php | grep wp_version

# Ar wlwmanifest.xml fiziškai egzistuoja diske
find /var/www -name "wlwmanifest.xml" 2>/dev/null

# Ar failas pasiekiamas per HTTP
curl -o /dev/null -w "%{http_code}" https://example.com/wp-includes/wlwmanifest.xml

Jei WordPress 6.3+ ir find nieko neranda — wlwmanifest.xml neegzistuoja. Tačiau nginx blokavimas vis tiek reikalingas: botai beldžiasi į šį kelią kiekvienoje svetainėje, o 403 vietoj 404 leidžia juos drausti per fail2ban.

DIAGNOSTIKA: KIEK UŽKLAUSŲ ATEINA

Patikrinti skenerių aktyvumą per paskutines 24 valandas:

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

Ieškoti šablono — dažnai tie patys IP adresai kreipiasi į wlwmanifest, xmlrpc ir wp-login iš eilės:

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

Pridėti wlwmanifest į WordPress fail2ban filtrą:

# /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 =

DAŽNOS KLAIDOS

Klaida 1: „Turiu WordPress 6.3+ — wlwmanifest manęs neliečia”

Liečia — dviem aspektais. Pirma: botai netikrina tavo WordPress versijos, jie beldžiasi į /wp-includes/wlwmanifest.xml kiekvienoje svetainėje. Be nginx blokavimo jie gauna 404 ir fiksuoja, kad čia WordPress. Antra: atnaujinus iš senesnės versijos failas gali fiziškai likti diske.

Klaida 2: „Failas grąžina 404 — vadinasi viskas gerai”

404 reiškia, kad botas gavo atsakymą iš serverio, užfiksavo kad skenavo šį adresą ir padarė išvadą — čia WordPress (nes tik WordPress diegimuose prašoma būtent šio kelio). 403 geriau — galima uždrausti tiems, kas prašo.

Klaida 3: „Pašalinau RSD per pirminės temos functions.php”

Pirminės temos functions.php pakeitimai neišgyvena temos atnaujinimų. Naudok mu-plugin arba vaiko temos functions.php.

PILNAS PATIKRINIMO KONTROLINIS SĄRAŠAS

# 1. wlwmanifest.xml nepasiekiamas tiesiogiai
curl -o /dev/null -w "%{http_code}" https://example.com/wp-includes/wlwmanifest.xml
# Tikimasi: 403

# 2. RSD endpoint užblokuotas
curl -o /dev/null -w "%{http_code}" "https://example.com/xmlrpc.php?rsd"
# Tikimasi: 403

# 3. RSD nuoroda nėra HTML
curl -s https://example.com/ | grep -c "EditURI"
# Tikimasi: 0

# 4. WordPress versija nematoma HTML
curl -s https://example.com/ | grep "generator.*WordPress"
# Tikimasi: tuščia išvestis

IŠVADA

wlwmanifest.xml ir RSD nėra pažeidžiamumai klasikine prasme. Jie nesuteikia užpuolikui tiesioginės prieigos. Tačiau jie suteikia informacijos: čia WordPress, čia xmlrpc.php, štai tikslus URL.

Automatizuotų atakų kontekste informacija = pranašumas. Botai dirba pagal parašų sąrašus. Pašalinus šias parašas, iškriti iš daugumos automatinių skenų.

Du veiksmai, penkios minutės:

# nginx: užblokuoti tiesioginę prieigą
location ~* /wp-includes/wlwmanifest\.xml {
    deny all;
    access_log off;
    log_not_found off;
}

# WordPress mu-plugin: pašalinti RSD ir versiją iš HTML
remove_action('wp_head', 'rsd_link');
remove_action('wp_head', 'wp_generator');

Tai ne stebuklingas sprendimas. Tai nereikalingų durų uždarymas, kurios vis tiek niekam nereikalingos.

Leave your thought here

El. pašto adresas nebus skelbiamas. Būtini laukeliai pažymėti *