WordPress XML-RPC:3 種停用方法 & 如何判別 xmlrpc.php 攻擊 | Jetpack 例外

聯盟行銷揭露 Ver 3.1
聯盟行銷揭露 Ver 3.2(僅限行動版)

WordPress 網站的存取紀錄中,如果同一個 IP 接連出現數百次 xmlrpc.php 請求,那就是針對 WordPress XML-RPC 的密碼暴力破解。本文分類了一台經營多個 WordPress 網站的 Nginx + Apache 架構伺服器一天的 8,624 筆存取紀錄,並實際套用三種停用方法、確認回應碼,整理出要在哪一層封鎖,才能連 PHP 的執行都減少。

XML-RPC 是透過 HTTP 收發 XML 文件、遠端呼叫 WordPress 功能的協定。從 WordPress 3.5 起,設定畫面中的開關選項被移除,安裝後就一直維持開啟狀態。

1️⃣ xmlrpc.php 的作用與使用場合

REST API 出現之後,很容易把 [xmlrpc.php] 當成已經沒人使用的檔案,但 Jetpack 至今仍透過這個檔案把網站連接到 WordPress.com。Jetpack 官方文件明確寫著它依賴 XML-RPC 連線,在預設封鎖 XML-RPC 的主機上,Jetpack 的連線本身就會失敗。這就像現在辦公室也還會保留一支傳真號碼一樣。

以 WordPress 7.1 來說,這個檔案接受的方法(method)共有 77 個,包括處理文章、媒體與留言的 wp 系列 50 個、舊部落格協定(MetaWeblog、MovableType、Blogger)23 個、Pingback 2 個,以及測試用的 demo 2 個。

wp 系列會用請求內容中的使用者名稱與密碼登入,因此不經過登入畫面。撰寫與修改文章、上傳媒體、管理留言都屬於這個系列;舊部落格協定的 23 個方法,則是為了用和其他部落格工具相同的方式收發文章而保留下來的。

demo.sayHello 是 WordPress XML-RPC 內建的測試用呼叫。送出後只會回傳一句 [Hello!],因為不需要登入,所以用來確認 XML-RPC 是否運作。與它成對的還有把兩個數相加後回傳的 demo.addTwoNumbers。

這些方法都必須用 POST 送出 XML 內容才會回應;用瀏覽器開啟網址,會收到 [XML-RPC server accepts POST requests only.] 訊息與 405。

Pingback 也是這個檔案負責的工作。發佈文章時,會向內文中所連結網站的 xmlrpc.php 發送 Pingback,接收端的伺服器則會直接開啟傳送端的網址,確認連結是否真的存在。

🧭 進入 xmlrpc.php 的請求與處理方式
📨 傳送端
↓
入口
xmlrpc.php
只處理以 POST 送出的 XML 內容
405 · XML-RPC server accepts POST requests only.
77 個方法
wp.*文章 · 媒體 · 留言 50 個
metaWeblog · mt · blogger舊部落格協定 23 個
pingback.*Pingback 2 個
demo.*確認運作用的測試呼叫 2 個
結果
維持與 WordPress.com 的連線
確認請求內容中的使用者名稱 · 密碼 → 撰寫 · 修改文章
伺服器直接開啟傳送端的網址確認連結
無需登入即以 Hello! 回應
不處理
Jetpack 透過這個檔案維持與 WordPress.com 的連線,所以封鎖這個檔案,連線就會中斷。
外部發佈工具會用放在請求內容中的使用者名稱與密碼登入,不會經過登入畫面。
向文章中連結的網站的 xmlrpc.php 發送 Pingback,接收端的伺服器會直接開啟傳送端的網址確認連結。
demo.sayHello 是確認 XML-RPC 是否運作的測試用呼叫,不需登入就會以一句 [Hello!] 回應。
用瀏覽器開啟網址,會收到 [XML-RPC server accepts POST requests only.] 訊息與 405。

2️⃣ 從存取紀錄看 xmlrpc.php 攻擊

密碼暴力破解不經過登入畫面,而是從 [xmlrpc.php] 進來。在一台經營多個 WordPress 網站的 Nginx + Apache 架構伺服器上,10 月 14 日 00:00 到 20:00 的存取紀錄中,POST /xmlrpc.php 請求有 8,624 次、IP 有 72 個,其中 19 個 IP 剛好各送了 300 次。

其中一個 IP 從 14:09 到 14:37,在 28 分鐘內送了 300 次,5~6 秒的間隔一直固定到最後。有 8,524 次請求在 User-Agent 中寫著 Jetpack 或 WordPress.com,同一個 IP 會輪流使用多個模仿真實 Jetpack 格式的值,例如 [Jetpack by WordPress.com (Jetpack 13.0; WordPress 6.3)]。

同一天 14:00 以前,安全外掛的 XML-RPC 關閉設定會以 302 把這些請求導回首頁;14:00 以後改在 Nginx 封鎖,就以 403 結束。302 代表即使回應內容是 0 位元組,每個請求仍執行了 PHP;403 則是不經過 PHP 的回應。

🧭 用一行存取紀錄看 xmlrpc.php 攻擊
說明圖解
🛡️ 伺服器狀態
一行存取紀錄(Nginx access.log)
203.0.113.25IP[14/Oct/2026:14:09:04 +0900]時間"POST /xmlrpc.php HTTP/1.1"要求302403狀態0153位元組"Jetpack by WordPress.com (Jetpack 13.0; WordPress 6.3)"User-Agent
執行 PHP · 302 導向首頁不經過 PHP · Nginx 預設 403 頁面
安全外掛關閉 XML-RPC 時,紀錄中會出現把請求導回首頁的 302。回應內容雖然是 0 位元組,但為了產生這個 302,PHP 仍會執行。
在 Nginx 封鎖後,紀錄中是 403 與 153 位元組的預設錯誤頁面,PHP 不會執行。
同一 IP 的請求
14:09:0414:37:20
28 分鐘 300 次,間隔 5~6 秒
00:00~20:00 POST 8,624 次IP 72 個各送 300 次的 IP 19 個冒充 Jetpack · WordPress.com 的 UA 8,524 次

3️⃣ system.multicall 與密碼暴力破解

很多說明都說 [system.multicall] 能在一次請求中測試數百個密碼,但在 WordPress 中,只要同一個請求裡的登入失敗一次,其餘呼叫的密碼就不會再比對。只有第一個呼叫會經過 wp_authenticate,之後的呼叫不經檢查,直接以相同的錯誤 [Incorrect username or password.] 回應。

每個呼叫仍會各自回傳回應,所以一個裝了 100 個錯誤密碼的請求,會在 0.12 秒內收到 23KB 的回應、其中有 100 個錯誤。在同一個請求中,如果把正確密碼放在後面,會因前面呼叫的失敗而一起被拒絕;放在前面的話,只有那個呼叫會成功。密碼只比對一次,但 XML 解析與 WordPress 載入在每個請求中都會重複,因此這種攻擊的伺服器負擔來自請求數量本身,而不是登入檢查。

4️⃣ 3 種停用 XML-RPC 的方法與回應碼

即使在外掛中關閉 XML-RPC,[xmlrpc.php] 請求仍會進到 PHP。在以篩選器關閉的狀態下送出含 100 個呼叫的請求,仍然原樣收到 HTTP 200 與 100 個錯誤(24KB)。依在哪一層拒絕,方法分為三種,各自的回應碼也不同。

1. 用程式碼以 xmlrpc_enabled 篩選器關閉

只要在佈景主題的 functions.php 中加入一行 add_filter,把 [xmlrpc_enabled] 篩選器設為 false 即可。在這個狀態下,需要登入的呼叫會以 [XML-RPC services are disabled on this site.] 錯誤與 HTTP 405 被拒絕,但不需要登入的測試呼叫 demo.sayHello 仍會以 200 回應。

2. 在外掛設定中關閉:Perfmatters · Hide My WP

這是在 Perfmatters、Hide My WP 等外掛中,用 Disable XML-RPC 設定停用的方法。Hide My WP 和上面一樣是篩選器方式,所以回應也相同;Perfmatters 則是在同一個篩選器之外,還會以 403 結束對 [xmlrpc.php] 的直接請求,並移除 Pingback 標頭與 Pingback 連結,因此關閉後 GET 與 POST 都會回應 403。不過,為了產生這個 403,WordPress 仍會執行到 init 階段,所以回應需要 0.13~0.15 秒,和開啟時的時間相同,PHP 的負擔依然存在。

📍 Settings › Perfmatters › General › Core › Disable XML-RPC
開啟切換開關後按下 [Save Changes],下方的訪客畫面就會改變。
管理後台
Settings saved.
perfmatters
⚙ GeneralVersion 2.6.8
Core
Disable Emojis?
Disable Dashicons?
Disable Embeds?
Disable XML-RPC?
Remove RSD Link?
Remove jQuery Migrate?
Hide WP Version?
Remove Shortlink?
Disable RSS Feeds?
Remove RSS Feed Links?
Disable Self Pingbacks?
Disable REST API?
Remove REST API Links?
Disable Google Maps?
Disable Password Meter?
Disable Comments?
Remove Comment URLs?
Add Blank Favicon?
Remove Global Styles?
Block Style Behavior?
Disable Heartbeat?
Heartbeat Frequency?
Limit Post Revisions?
Autosave Interval?
Login URL
Custom Login URL?
Disabled Behavior?
Message?
📍 Hide My WP › Settings › Cleanup › Disable XML RPC
勾選後按下 [Save Settings],下方的訪客畫面就會改變。
管理後台
Hide My WP
Settings was updated successfully!
Cleanup
Minify HTML
Remove comments and whitespaces from HTML (Use with cache).
Minify style
Remove theme info and other comments from stylesheet. (Require new style name)(Use with cache!).
Replace \/ URLs
Choose if you see Javascript URLs (e.g. \/wp-content\/themes)
Feed Meta
Other Meta
Default Tagline
Remove Version
Body Classes
Menu Classes
Post Classes
Clean up other
Replace in AJAX
Change Nonce
Disable XML RPC
Internal CSS
Useful to paste internal CSS code (Require New Theme Path)
Internal JS
Useful to paste internal JS code (Require New Theme Path)
未登入訪客的畫面 — 以儲存的設定送出請求
405
XML-RPC server accepts POST requests only.
GET 請求在任何設定下都是 405。
200
Hello!
這是確認運作用的測試呼叫,所以不需登入就會回應。
200
faultCode 403 · Incorrect username or password.
會執行密碼比對。即使變更登入網址也一樣。
403
無內容
Perfmatters 在 init 階段以 403 結束。PHP 仍會執行。
403
無內容
Perfmatters 在 init 階段以 403 結束。PHP 仍會執行。
403
無內容
不比對密碼,直接以 403 結束。PHP 仍會執行。
200
Hello!
因為是篩選器方式,不需要登入的呼叫仍照常回應。
405
faultCode 405 · XML-RPC services are disabled on this site.
只有需要登入的呼叫會被拒絕。

3. 在網頁伺服器封鎖:Nginx · Apache

無論 Nginx 或 Apache,都會在 PHP 之前拒絕請求,所以和外掛方式不同,WordPress 不會執行。

# Nginx — server 區塊內,放在處理 PHP 的 location 上方
location = /xmlrpc.php {
    deny all;
}

這個 location 區塊要放在處理 PHP 的 location 上方,Nginx 會直接拒絕與 [xmlrpc.php] 完全相符的請求。加入後,必須先檢查設定再重新載入,才會生效。

nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
systemctl reload nginx

重新載入之後,GET 與 POST 都不會到達 PHP,而是以 403 結束;回應內容是 Nginx 預設的 403 頁面,153 位元組,回應時間為 0.02 秒。

# Apache — 網站根目錄的 .htaccess(Apache 2.4)
<Files xmlrpc.php>
    Require all denied
</Files>

只使用 Apache 的伺服器,把上面的 Files 指令放進網站根目錄的 .htaccess 就會立即生效,同樣在 0.02 秒內回傳 Apache 預設的 403 頁面(199 位元組)。

curl -s -o /dev/null -w '%{http_code}' -X POST https://example.com/xmlrpc.php -d '<methodCall><methodName>demo.sayHello</methodName></methodCall>'
403

上面的指令會用 POST 送出測試呼叫 demo.sayHello 來確認回應碼,在網頁伺服器封鎖後會回傳 403。拿掉 [-o /dev/null] 與 [-w] 選項再送出,就能看到回應內容:收到 [Hello!] 代表是開啟狀態;不過只用篩選器關閉的網站也會收到相同的回應,所以要連登入呼叫一起送出,才能確認 405。

5️⃣ 不該關閉的情況與替代方案

在網頁伺服器整個封鎖的效果最大,但不適合透過 Jetpack 與 WordPress.com 連接的網站。Jetpack 依賴 XML-RPC 連線,在 WP Engine 這類預設封鎖 XML-RPC 的主機上,必須依照 Jetpack 的說明開放例外才能連線。想接收 Pingback 的網站也一樣。

這類網站可以只允許 Jetpack 公開的 IP 清單(jetpack.com/ips-v4.txt),拒絕其餘請求,藉此縮小範圍。在 Nginx 的 location 區塊中,依 IP 網段逐行加入 allow,最後放上 deny all 即可。

如果無法完全封鎖,還剩下一個方法:對未登入的請求設定每分鐘次數限制。設定方式和 wp-admin 安全 一文中的 Nginx 速率限制項目相同,而且只計算沒有登入 Cookie 的請求,所以不會影響已登入的管理員作業。

🔢 FAQ & 推薦內容

即使刪除,更新 WordPress 核心時,套件裡的檔案也會原樣重新複製回來,所以下次更新時就會恢復。比起刪除檔案,在網頁伺服器封鎖該網址或用篩選器停用的做法,在更新後也會持續有效。

REST API 是從 [wp-json] 網址進入的另一個入口,所以即使停用 XML-RPC,它仍然開著。區塊編輯器會使用這個入口,因此正確的做法不是完全停用,而是只封鎖登出狀態的請求。

不會先確認,直接就送。每個 WordPress 網站的這個檔案都在相同路徑,所以掃描程式只要換網址就能送出 POST。預設狀態的 WordPress 甚至會透過頁面 head 中的 [EditURI] 連結,以及接受 Pingback 的文章的 [X-Pingback] 標頭,自己公開 xmlrpc.php 的網址。

🛡️ WordPress 安全推薦內容 文章 8 · 外部文章 2
🗂️ WordPress 安全
📋 wp-admin 安全 📄 暴力破解 9 種對策 📄 Hide My WP 📄 安全外掛 Best 5
📄 停用 XML-RPC 📄 REST API 📄 Pingback · Trackback 📄 wp-config.php 安全
📡 Nanaco Japan 的 xmlrpc.php 因應方法 📡 サイトの主治医:xmlrpc.php 停用與確認
沒有搜尋結果。

ℹ️ 聯盟行銷說明
本站內容包含聯盟行銷連結。訪客透過這些連結購買商品或服務時,本站會從賣家獲得佣金。在這個過程中,購買者支付的金額(遇到活動折扣時金額會降低↓)並不會因此提高。刊登的價格、折扣與庫存資訊以撰寫當時為準,可能與實際情況不同,購買前請向賣家做最後確認。商品的挑選與評價依本站自訂的標準撰寫,是否支付佣金不會影響介紹順序或評價內容。