攻擊者要闖進你的 WordPress 後台,第一步往往不是猜密碼,而是先弄清楚「帳號叫什麼」。WordPress 預設會從好幾個地方把使用者名稱送出去:網址列加上 ?author=1、打開 REST API 的使用者端點、甚至登入失敗的錯誤訊息,都能讓人不費吹灰之力列出站內所有帳號。這個動作就叫使用者列舉(user enumeration),它本身不算入侵,卻是後續暴力破解、密碼噴灑、釣魚信件最關鍵的前置情報。
WordPress 使用者列舉防護的核心,就是把這幾個外洩管道一個個堵起來,讓攻擊者連帳號名稱都拿不到。本文會說明列舉是怎麼運作的、台灣站長最常忽略的幾個漏洞點,並提供可直接貼進佈景主題或外掛的程式碼,最後教你用兩道指令自行驗證有沒有真的擋住。
使用者列舉是什麼,為什麼只露出帳號就很危險
使用者列舉是攻擊者透過公開、不需登入的管道,把網站上有效的使用者名稱一個個挖出來的偵查手法。它不會直接破壞網站,但會把入侵的難度大幅拉低。
差別在哪?沒有帳號名稱時,攻擊者得同時猜「帳號」和「密碼」兩個變數,組合數量龐大到幾乎不可能成功。一旦拿到一份有效帳號清單,問題就從「猜兩個東西」縮小成「只猜密碼」。配合外流的密碼字典,原本要幾年才可能撞中的攻擊,可能幾分鐘內就見效。
真實世界的自動化攻擊正是這樣分兩步走。先派機器人掃描全站,列出所有有效帳號;接著對這些帳號跑密碼噴灑,拿常見密碼與其他網站外洩的帳密逐一測試。比起亂槍打鳥,這種「先列舉、再噴灑」的兩段式做法效率高出許多,也是多數 WordPress 站台被攻陷的起點。
更隱蔽的風險是針對性攻擊。如果攻擊者知道某個帳號對應的是站長本人或某位作者,就能拿這個名字去查他在其他服務有沒有重複用密碼,或量身打造一封看起來很可信的釣魚信。許多入侵的源頭不是網站本身有漏洞,而是某個完全無關的服務外洩了密碼,攻擊者靠列舉把人對上了號。
WordPress 會從哪些地方把帳號名稱洩漏出去
WordPress 預設開啟了好幾個會吐出使用者名稱的管道,多數站台這些口子全是敞開的。先認清每一個來源,才知道要堵哪裡。
- 作者彙整頁與
?author=N:WordPress 為每位有發文的使用者建立/author/帳號名稱/形式的彙整頁。攻擊者在網址後面接上?author=1、?author=2依序遞增,WordPress 會把它重新導向到對應的作者頁,網址裡就直接寫著帳號名稱。腳本能在幾秒內把整站帳號掃完。 - REST API 使用者端點:在網址後面接
/wp-json/wp/v2/users,會回傳一份 JSON,列出所有發過文的使用者,包含顯示名稱、頭像,以及最關鍵的slug欄位。預設不需要任何身分驗證就能存取。 - 登入錯誤訊息:輸入不存在的帳號,WordPress 提示「使用者名稱未註冊」;輸入正確帳號但錯誤密碼,提示卻變成「密碼錯誤」。這個措辭差異等於替攻擊者確認了帳號是否存在。
- 網站地圖(sitemap):預設的
wp-sitemap.xml會列出所有作者頁網址,等於把帳號名稱整理成一份清單送上門。 - oEmbed 內嵌資料:當別的網站要內嵌你的文章時,WordPress 回傳的資料裡帶有作者名稱與作者網址,也是一個被忽略的外洩點。
- XML-RPC:
xmlrpc.php端點除了能被拿來放大暴力破解,部分方法的回應差異也能用來確認帳號是否有效。
特別要釐清一個常見誤解:很多人以為只要把使用者的「顯示名稱」改掉就安全了。事實上 REST API 真正洩漏登入帳號的是 slug 欄位,而 slug 預設取自 user_nicename,常常就等於登入帳號本身。顯示名稱換了,slug 沒換,帳號照樣外洩。
怎麼先測自己的站有沒有在漏帳號
動手修之前,先確認站台目前漏不漏,修完也好對照。下面兩道指令不需要登入,對任何 WordPress 站台都能跑。
第一道測 REST API 端點:
curl -s "https://你的網域/wp-json/wp/v2/users/"
如果回傳的 JSON 裡出現一連串使用者物件,且帶有 slug 與 name 欄位,代表你的站正透過 REST API 洩漏帳號。
第二道測作者 ID 重導:
curl -sI "https://你的網域/?author=1"
如果回應的標頭裡出現 Location: 指向 /author/某帳號/ 的 301 重導,代表作者彙整頁這條路也在漏。
多數沒做過防護的站台兩道測試都會中。好消息是兩個洞都不難補,後面的程式碼貼上去通常五分鐘內搞定。
怎麼擋掉 ?author=N 與作者彙整頁的列舉
作者頁是最容易被利用、也最該優先處理的一條路。處理方式有兩種思路:一種是攔截帶 author 參數的請求,另一種是直接讓作者彙整頁回 404。
如果你的站沒有「依作者列出文章」的需求(單人部落格、品牌官網、純電商幾乎都不需要),最乾淨的做法是讓作者頁整個失效。把下面這段加進佈景主題的 functions.php,或更建議放進一個站台專用的小外掛:
add_action( 'template_redirect', function () {
if ( is_author() || ( isset( $_GET['author'] ) && ! is_user_logged_in() ) ) {
global $wp_query;
$wp_query->set_404();
status_header( 404 );
nocache_headers();
}
} );
這段會把任何作者彙整頁請求轉成 404,攻擊者什麼帳號都看不到,而一般訪客本來就用不到作者頁,不受影響。
如果你跑的是 Apache,也可以在 WordPress 載入前就於伺服器層攔下來,效率比 PHP 方案更好。把下面這段放進站台根目錄 .htaccess,位置要在 WordPress 既有的重寫規則之前:
RewriteCond %{QUERY_STRING} ^author=d+ [NC]
RewriteRule .* /? [R=301,L]
它會抓出任何 author= 後面接數字的請求,重導回首頁。要提醒的是,這條規則對已登入的後台操作也一視同仁,所以若你後台有依賴 author 參數的流程,請改用上面的 PHP 方案並保留 is_user_logged_in() 判斷。
順帶把網站地圖裡的作者清單也一起拔掉,免得 sitemap 又把帳號送出去:
add_filter( 'wp_sitemaps_add_provider', function ( $provider, $name ) {
if ( 'users' === $name ) {
return false;
}
return $provider;
}, 10, 2 );
怎麼關掉 REST API 的使用者端點而不弄壞區塊編輯器
REST API 的使用者端點是列舉效率最高的一條路,一個請求就能拿到整份名單,所以一定要處理。但這裡有個容易踩的雷:不能粗暴地把整個使用者端點關死,因為 WordPress 的區塊編輯器(Gutenberg)與不少外掛在後台運作時,本來就需要讀取使用者資料。
正確的做法是「只對未登入者關閉」。已登入的管理員、編輯透過後台操作時帶有驗證 cookie,照樣能存取;只有沒登入的訪客會被擋下來。把下面這段加進 functions.php 或站台專用外掛:
add_filter( 'rest_endpoints', function ( $endpoints ) {
if ( ! is_user_logged_in() ) {
if ( isset( $endpoints['/wp/v2/users'] ) ) {
unset( $endpoints['/wp/v2/users'] );
}
if ( isset( $endpoints['/wp/v2/users/(?P<id>[d]+)'] ) ) {
unset( $endpoints['/wp/v2/users/(?P<id>[d]+)'] );
}
}
return $endpoints;
} );
加上去之後,未登入者請求 /wp-json/wp/v2/users 會得到 404 或空回應,後台與區塊編輯器則完全不受影響。這是最平衡的做法,既擋住列舉又不犧牲功能。
要不要在 .htaccess 層再加一道伺服器級封鎖,取決於你的架構。純展示型的網站可以加;但如果你跑的是無頭(headless)前端,或有第三方服務需要讀使用者端點,伺服器層的一刀切會把這些合法用途也擋掉,這種情況請只用上面的 PHP 方案。
別忘了 oEmbed 也會帶出作者資訊,一併處理:
add_filter( 'oembed_response_data', function ( $data ) {
unset( $data['author_url'] );
unset( $data['author_name'] );
return $data;
} );
怎麼讓登入錯誤訊息不再替攻擊者確認帳號
登入表單是另一條常被忽略的列舉管道。WordPress 預設會針對「帳號不存在」與「密碼錯誤」回傳不同的提示,等於主動告訴攻擊者哪個帳號是真的。
解法很單純:讓所有登入失敗都回同一句通用訊息,攻擊者就無從判斷是帳號錯還是密碼錯。
add_filter( 'login_errors', function () {
return '登入失敗,請確認帳號與密碼是否正確。';
} );
要留意這個篩選器會覆蓋所有登入錯誤訊息。如果你裝了驗證碼(CAPTCHA)類外掛,驗證碼錯誤時也只會顯示這句通用訊息,使用者可能一時不知道是哪裡出錯。若這點對你的站台是困擾,可以改寫成只在帳號或密碼錯誤時才覆蓋訊息,其餘狀況維持原樣。
另一個能順手做的硬化動作是讓使用者的 slug 和登入帳號脫鉤。在後台編輯使用者時把「顯示名稱」設成跟登入帳號不同的字串,並透過程式或資料庫調整 user_nicename,作者頁網址與 REST API 的 slug 就不會直接洩漏真正的登入名稱。即使某個管道沒堵乾淨,攻擊者拿到的也只是 slug,不是能直接拿去登入的帳號。
自己寫程式還是裝外掛比較好
兩條路都行,差別在你願不願意維護程式碼。前面的程式碼片段優點是輕量、可控、不增加額外外掛負擔,缺點是換佈景主題或升級時要記得帶著走,所以強烈建議放進站台專用的小外掛,而不是塞在佈景主題的 functions.php,這樣換主題也不會把防護一起換掉。
如果你不想碰程式碼,幾款安全外掛也能把上述防護自動套上。Stop User Enumeration 是專注做這件事的輕量外掛,鎖定作者掃描與 REST API 使用者清單,安裝即生效。若想要更全面的防護套件,Solid Security(前身 iThemes Security)、Wordfence、All In One WP Security 都把使用者列舉防護整合在更大的安全功能裡,連同雙因素驗證、登入次數限制、防火牆一起管理。
選外掛時的重點是:使用者列舉防護只是整個登入安全的其中一塊。光擋住列舉還不夠,你仍然需要暴力破解防護、雙因素驗證、登入監控這些配套,才算真正把後台守住。挑一款能把這些一起涵蓋的外掛,會比東補一個西補一個來得省事。
修完之後怎麼確認真的擋住了
改完設定別急著收工,回頭用前面那兩道 curl 指令再驗一次,這是最容易被略過卻最重要的一步。
開一個無痕視窗(確保未登入狀態),依序檢查:
- 作者頁測試:連到
https://你的網域/?author=1。防護生效的話,應該看到 404 或被導回首頁,而不是出現網址帶帳號名稱的作者頁。 - REST API 測試:在未登入狀態打開
https://你的網域/wp-json/wp/v2/users。應該得到 404 或空回應,而不是一份帶帳號的使用者清單。 - 登入頁測試:先用一個不存在的帳號嘗試登入,再用真實帳號配錯誤密碼登入。兩次應該顯示完全相同的錯誤訊息。
三項都通過,才代表防護確實到位。如果有任何一項還在漏,回頭檢查程式碼有沒有被佈景主題覆蓋、外掛設定有沒有勾對,或伺服器有沒有快取舊回應。
把使用者列舉防護當成登入安全的地基,上面再疊強密碼政策、雙因素驗證、登入次數限制,整體防護才站得穩。攻擊者永遠先挑軟柿子下手,一個連帳號名稱都問不出來的網站,已經比網路上多數站台難啃得多。從現在就把這幾個外洩管道堵起來,是花最少力氣換最多安全的一筆投資。