D
P
0

WordPress & PHP

Sudah `add_filter('xmlrpc_enabled', '__return_false')` tapi `xmlrpc.php` Masih Balas 200? Filter Itu Tidak Mematikan XML-RPC

30 Juni 2026·3 menit baca
Sudah `add_filter('xmlrpc_enabled', '__return_false')` tapi `xmlrpc.php` Masih Balas 200? Filter Itu Tidak Mematikan XML-RPC

Saat melakukan hardening di sebuah situs WordPress milik klien, salah satu item di checklist saya adalah mematikan XML-RPC. Endpoint lawas itu nyaris tidak dipakai lagi, hampir semua integrasi modern sudah lewat REST API, tapi dia masih jadi pintu favorit untuk brute force. Jadi saya pasang snippet klasik yang beredar di mana-mana:

add_filter('xmlrpc_enabled', '__return_false');

Centang di checklist. XML-RPC mati. Setidaknya itu yang saya kira, dan itu juga yang tersirat dari nama filternya.

Beberapa waktu kemudian, hasil security scan untuk situs itu mampir ke meja saya. Satu temuannya bikin saya berhenti scroll: POST /xmlrpc.php dengan system.listMethods masih dibalas HTTP 200, lengkap dengan daftar method. Saya tidak percaya begitu saja, jadi saya cek sendiri dari terminal:

curl -s -X POST \
  -d '<methodCall><methodName>system.listMethods</methodName></methodCall>' \
  https://old-site.com/xmlrpc.php

Dan benar. Respons XML berisi puluhan method, dari system.multicall sampai pingback.ping. Endpoint yang saya kira sudah mati itu masih hidup, ramah, dan dengan senang hati memberi tahu siapa pun apa saja yang bisa dipanggil.

Kenapa ini terjadi

Root cause-nya sederhana dan agak menyebalkan: filter xmlrpc_enabled tidak melakukan apa yang namanya janjikan. Filter itu hanya menonaktifkan subset method yang butuh autentikasi, jalur publishing macam wp.getUsersBlogs dan kawan-kawannya. Endpoint-nya sendiri tetap berdiri. Method system.* dan pingback.* tidak lewat gerbang itu sama sekali, jadi mereka tetap merespons seperti biasa. Dokumentasi WordPress sebenarnya jujur soal ini, di kalimat yang jarang dibaca orang: filter itu memang cuma untuk method XML-RPC yang memerlukan autentikasi.

Konsekuensinya bukan sekadar kosmetik. system.multicall memungkinkan penyerang membungkus ratusan percobaan login ke dalam satu request HTTP, amplifikasi brute force klasik. Dan pingback.ping bisa disalahgunakan supaya server kamu ikut menembak situs lain dalam serangan DDoS. Dua vektor paling terkenal dari XML-RPC, dan dua-duanya masih terbuka setelah filter yang katanya "disabled" itu terpasang.

Nama filter itu oversell. xmlrpc_enabled terdengar seperti saklar utama satu rumah, padahal dia cuma saklar lampu satu kamar.

Perbaikannya

Karena satu kunci sudah terbukti bohong, sekarang saya melapis. Lapis pertama: kosongkan seluruh tabel method lewat xmlrpc_methods, filter yang benar-benar memegang daftar lengkapnya. Lapis kedua: matikan endpoint-nya sedini mungkin, sebelum WordPress sempat mem-parse payload XML apa pun.

// Layer 1: strip every XML-RPC method, including system.* and pingback.*.
add_filter('xmlrpc_methods', '__return_empty_array');
 
// Layer 2: short-circuit the endpoint before it does any real work.
add_action('plugins_loaded', function () {
    if (defined('XMLRPC_REQUEST') && XMLRPC_REQUEST) {
        status_header(403);
        exit;
    }
}, 0);

Kenapa dua-duanya? xmlrpc_methods berjalan di atas tabel method final, jadi mengembalikan array kosong berarti system.listMethods tidak punya apa-apa untuk dilaporkan dan tidak ada satu pun yang bisa dipanggil. Tapi kalau suatu hari ada plugin yang mendaftarkan ulang method-nya sendiri, lapis kedua tetap menahan: WordPress mendefinisikan konstanta XMLRPC_REQUEST sangat awal saat xmlrpc.php menangani request, dan hook plugins_loaded di prioritas 0 keluar duluan sebelum ada kerja lain.

Mumpung sedang di file hardening yang sama, saya sekalian menutup temuan info-disclosure dari scan yang sama: versi WordPress yang bocor lewat meta generator, plus file bawaan yang mengumumkan versi ke siapa saja yang mampir.

// Hide the WordPress version from the generator meta tag and feeds.
remove_action('wp_head', 'wp_generator');
add_filter('the_generator', '__return_empty_string');
# .htaccess: 404 the stock info-disclosure files
RedirectMatch 404 ^/(readme\.html|license\.txt)$

Verifikasi

Ini bagian yang dulu saya lewati dan tidak akan saya lewati lagi. Jalankan ulang request yang sama persis dengan yang dipakai scanner, lalu lihat status code-nya:

curl -s -o /dev/null -w '%{http_code}\n' -X POST \
  -d '<methodCall><methodName>system.listMethods</methodName></methodCall>' \
  https://old-site.com/xmlrpc.php

Sekarang balasannya 403, tanpa daftar method, tanpa XML sama sekali. Itu bukti. Bukan asumsi, bukan nama filter yang terdengar meyakinkan.

Pelajaran

Kesalahan saya bukan salah pasang snippet; snippet-nya bekerja persis seperti yang didesain. Kesalahan saya adalah memverifikasi hardening dengan membaca nama filter, bukan dengan mengirim request sungguhan. Kalau kamu mau XML-RPC benar-benar mati, xmlrpc_enabled saja tidak cukup: kosongkan xmlrpc_methods atau blokir endpoint-nya langsung, idealnya dua-duanya. Lalu tutup dengan satu curl ke xmlrpc.php dan pastikan matanya sendiri yang melihat penolakan itu. Sejak insiden ini, aturan saya sederhana: hardening belum selesai sampai request aslinya ditolak di depan mata saya sendiri.