Bali, Indonesia
Insights

Ada Lonjakan Request ke xmlrpc.php? Cek Access Log Sebelum Memblokir

November 1, 2026
0:00
Server WordPress tiba-tiba berat, load naik, dan begitu access log dibuka isinya penuh POST /xmlrpc.php. Reaksi pertama biasanya langsung memblokir file itu. Masalahnya, xmlrpc.php bukan cuma pintu masuk penyerang: Jetpack, aplikasi WordPress di HP, dan beberapa tool publishing juga lewat situ. Sebelum memblokir, ada baiknya kita pastikan dulu dari log siapa yang sebenarnya mengetuk. Artikel ini fokus ke pengecekan yang bisa kamu ulang sendiri di terminal: hitung volume, lihat pola IP dan user-agent, baca ukuran response, lalu verifikasi lagi setelah aturan dipasang.
XML-RPC adalah protokol lama untuk memanggil fungsi di server lewat HTTP, dengan body request berformat XML. WordPress menyediakannya lewat xmlrpc.php supaya aplikasi luar bisa login, membuat post, atau mengambil data tanpa lewat halaman admin. Yang membuatnya menarik bagi penyerang adalah method system.multicall. Method ini mengizinkan banyak pemanggilan digabung dalam satu request. Untuk brute-force, artinya satu POST bisa berisi banyak percobaan username dan password sekaligus, misalnya lewat wp.getUsersBlogs. Dari sisi log, kelihatannya cuma satu baris, padahal di dalamnya ada puluhan tebakan. Dari sisi server, tiap request ke xmlrpc.php memuat WordPress penuh: plugin, theme, koneksi database. Jadi walaupun semua tebakan gagal, tiap request tetap memakan satu worker PHP selama diproses. Kalau datangnya cepat dan terus-menerus, worker habis dan pengunjung biasa ikut antre. Kalau gejalanya sudah sampai situ, pembahasan PHP-FPM pool starvation relevan untuk dibaca berdampingan. Satu hal yang sering bikin bingung: XML-RPC biasanya membalas HTTP 200 walaupun login gagal. Pesan gagalnya ada di dalam body sebagai fault. Jadi status 200 di log bukan tanda penyerang berhasil masuk, dan juga bukan tanda request itu sah.
Langkah pertama adalah memastikan seberapa besar sebenarnya lonjakan ini. Lokasi log tergantung setup: Nginx umumnya di /var/log/nginx/access.log, Apache di Debian/Ubuntu di /var/log/apache2/access.log, di keluarga RHEL di /var/log/httpd/access_log, sedangkan di cPanel log per domain ada di folder domlogs yang path-nya bisa berbeda antar versi. Perintah di bawah mengasumsikan format log combined yang umum dipakai. Semuanya read-only.
BASH
# Sesuaikan path log dengan server kamu
LOG=/var/log/nginx/access.log

# 1. Total POST ke xmlrpc.php
grep -c 'POST /xmlrpc.php' "$LOG"

# 2. Top 20 IP pengirim
grep 'POST /xmlrpc.php' "LOG" | awk '{print1}' | sort | uniq -c | sort -rn | head -20

# 3. Top user-agent (field ke-6 kalau dipisah tanda kutip ganda)
grep 'POST /xmlrpc.php' "LOG" | awk -F'"' '{print6}' | sort | uniq -c | sort -rn | head -10

# 4. Kombinasi status dan ukuran response (byte)
grep 'POST /xmlrpc.php' "LOG" | awk '{print9, $10}' | sort | uniq -c | sort -rn | head -10

# 5. Jumlah request per menit (20 menit terakhir di log)
grep 'POST /xmlrpc.php' "LOG" | awk '{print substr(4,2,17)}' | uniq -c | tail -20
Sebelum membaca hasilnya, cek satu hal: apakah situs ini di belakang Cloudflare atau reverse proxy lain. Kalau iya dan web server belum dikonfigurasi untuk membaca IP asli, kolom IP di log akan berisi IP proxy, bukan IP pengunjung. Analisis per IP jadi menyesatkan, dan memblokir IP itu bisa memutus seluruh situs. Perlu dicatat juga, kolom ukuran di format combined adalah ukuran response yang dikirim server, bukan ukuran body request. Kalau kamu butuh ukuran request, tambahkan $request_length ke log_format di Nginx, atau %I di LogFormat Apache (butuh mod_logio). Itu perubahan config, jadi lakukan dengan sadar dan reload setelah dites.
Dari lima perintah tadi, polanya biasanya cukup jelas kalau dibaca bersamaan, bukan satu-satu. Pola brute-force umumnya punya ciri seperti ini. Ritmenya rata dan tinggi per menit, tidak mengikuti jam aktif pemilik situs. User-agent sering kosong, generik, atau meniru browser desktop, padahal browser tidak pernah POST ke xmlrpc.php dalam pemakaian normal. IP-nya bisa sedikit tapi sangat aktif, atau justru tersebar ke ratusan IP dengan masing-masing beberapa request saja. Pola tersebar ini yang paling licin, karena pemblokiran per IP hampir tidak berpengaruh. Ukuran response juga memberi petunjuk. Request system.multicall dengan banyak tebakan akan dibalas dengan banyak fault sekaligus, sehingga ukuran response cenderung lebih besar dan seragam dibanding balasan single call. Kalau di perintah keempat kamu melihat satu kombinasi status dan ukuran yang mendominasi, misalnya ribuan baris dengan 200 dan ukuran persis sama, itu tanda kuat request-nya dibuat oleh skrip yang sama. Ini rule of thumb, bukan bukti pasti, karena isi body tidak tercatat di log. Trafik yang sah biasanya terlihat berbeda. Aplikasi WordPress di HP umumnya mengirim user-agent yang mengandung nama aplikasinya (biasanya ada kata seperti wp-android atau wp-iphone), datang dari IP seluler pemilik situs, dan volumenya kecil serta mengikuti jam pemakaian. Jetpack terhubung dari server milik penyedianya. Untuk memastikannya, cocokkan IP dan user-agent di log dengan rentang IP yang dipublikasikan di dokumentasi resmi Jetpack, jangan menebak dari nama hostname saja. Pertanyaan yang biasanya dicek support engineer pertama kali ke pemilik situs sederhana saja: apakah Jetpack terpasang dan aktif, dan apakah ada yang posting lewat aplikasi atau tool eksternal. Kalau jawabannya tidak untuk semuanya, keputusan berikutnya jadi jauh lebih mudah. Kalau kamu ingin melihat apakah XML-RPC memang aktif dan merespons, panggil method yang tidak butuh login dari mesin kamu sendiri:
BASH
# Cek apakah xmlrpc.php merespons (read-only, tanpa login)
curl -s -o /dev/null -w '%{http_code} %{size_download}
' \
  -d '<methodCall><methodName>system.listMethods</methodName></methodCall>' \
  https://domain-kamu.tld/xmlrpc.php

# Lihat sebagian daftar method, termasuk apakah system.multicall tersedia
curl -s -d '<methodCall><methodName>system.listMethods</methodName></methodCall>' \
  https://domain-kamu.tld/xmlrpc.php | grep -o 'system\.[a-zA-Z]*' | sort -u
Simpan output perintah pertama. Itu jadi pembanding setelah aturan dipasang.
Kalau tidak ada Jetpack, aplikasi mobile, atau integrasi lain yang bergantung pada XML-RPC, blokir penuh di level web server adalah pilihan paling sederhana dan paling ringan. Kata kuncinya di level web server. Plugin atau filter xmlrpc_enabled di WordPress memang menonaktifkan method yang butuh autentikasi, tapi request tetap sampai ke PHP dan tetap memuat WordPress. Beban worker tidak banyak berkurang. Kalau XML-RPC masih dipakai, pilihannya adalah membatasi: allowlist IP yang sah (misalnya rentang IP Jetpack yang sudah kamu cek), atau rate limit per IP supaya satu sumber tidak bisa menghabiskan worker. Rate limit kurang efektif untuk serangan yang tersebar ke banyak IP, jadi untuk pola itu allowlist biasanya lebih masuk akal. Contoh untuk Nginx. Pilih salah satu varian, jangan dua-duanya:
NGINX
# Varian A: blokir penuh (di dalam blok server)
location = /xmlrpc.php {
    deny all;
}

# Varian B: rate limit per IP
# Baris ini di blok http (contoh angka, sesuaikan dengan trafik):
# limit_req_zone $binary_remote_addr zone=xmlrpc:10m rate=1r/m;
# Lalu di blok server:
# location = /xmlrpc.php {
#     limit_req zone=xmlrpc burst=5 nodelay;
#     # salin include dan fastcgi_pass yang sama dengan location PHP kamu
# }
Varian B harus meneruskan request ke PHP dengan cara yang sama seperti location PHP utama, dan detailnya tergantung konfigurasi masing-masing server, jadi salin dari config yang sudah ada, jangan menulis ulang dari nol. Secara default, request yang ditolak limit_req dibalas 503. Untuk Apache 2.4, padanannya adalah blok <Files "xmlrpc.php"> berisi Require all denied, diletakkan di .htaccess atau di config virtual host. Kalau mau allowlist, ganti dengan Require ip diikuti rentang IP yang sah. Peringatan: perubahan config web server berlaku untuk semua situs di virtual host atau server tersebut. Selalu jalankan nginx -t atau apachectl configtest sebelum reload. Kalau tes gagal dan kamu tetap reload atau restart, situs bisa down. Simpan salinan file config sebelum mengedit supaya mudah dikembalikan.
Aturan yang sudah dipasang belum tentu berlaku, misalnya karena ada location lain yang lebih dulu cocok, atau .htaccess tidak dibaca karena AllowOverride mati. Jadi ulangi pengecekan dengan cara yang sama. Jalankan lagi perintah curl pertama dari luar server. Untuk blokir penuh, hasilnya seharusnya 403 dengan ukuran kecil. Untuk rate limit, beberapa request pertama masih lolos lalu sisanya mulai ditolak. Kalau masih 200 dengan ukuran sama seperti sebelumnya, aturannya belum aktif. Setelah itu, tunggu beberapa menit dan jalankan lagi perintah nomor 4 dan 5 dari blok pertama. Kombinasi status di log harus berubah, dari dominan 200 menjadi 403 atau 503. Penyerang biasanya tidak langsung berhenti, jadi jumlah request per menit mungkin tetap tinggi, tapi yang penting request itu sekarang dijawab web server tanpa menyentuh PHP. Konfirmasi dampaknya dengan melihat jumlah proses PHP-FPM dan load server. Kalau masih ada error 502 setelahnya, cara membaca log Nginx dan PHP-FPM membantu menelusuri apakah ada penyebab lain. Terakhir, kalau Jetpack atau aplikasi mobile masih dipakai, minta pemilik situs mencoba fitur yang bergantung pada XML-RPC. Lebih baik ketahuan sekarang daripada menunggu keluhan bahwa posting dari HP gagal. Intinya, lonjakan POST ke xmlrpc.php adalah gejala, bukan diagnosis. Lima menit membaca IP, user-agent, ritme, dan ukuran response di log biasanya sudah cukup untuk tahu apakah itu skrip brute-force atau layanan yang sah. Blokir penuh di web server kalau tidak ada yang bergantung padanya, batasi dengan allowlist atau rate limit kalau masih dipakai, dan selalu ulangi pengecekan yang sama setelahnya untuk membuktikan aturannya benar-benar bekerja.