前言:來自 WordPress 官方的一封信

2026 年 3 月的一個清晨,我收到了一封來自 WordPress.org 插件審核團隊的郵件。標題赫然寫著關於我的外掛 Liaison Site Prober 存在安全性風險,這個漏洞的編號:CVE-2026–3569

作為一名深耕 WordPress 生態系統的後端工程師以及資安從業人員,這真是個火辣的巴掌,巴掌聲在我心中清澈響亮,蕩氣迴腸。

開發背景:為什麼開發 Liaison Site Prober?

我發現許多組織、機關、企業使用的 PHP 網站大多都年代久遠,當初應該是找接案公司套套模板上線,往往未考量異動日誌與效能監控。或是當初工程師有留些基本的純文字日誌功能,但只有工程師自己會用。幾年後,當初建置網站的負責人離職,小單位預算不多,網站能用就好,原接案公司可能收掉了,人事全非。

當發現安全漏洞時,首先是買個防火牆看能不能擋(最低價得標),但維護就很兩光,很多對外 Policy 都是 all-to-all,很通透。基於個人興趣,我整理了過往經驗開發了 Liaison Site Prober。出發點是練功外,順便幫助開發者追蹤後端活動並優化系統穩定性,自助助人。安全性稽核外掛比一般外掛更要注意安全性,不然一不注意,稽核資訊內含主機位址、使用者資訊全部露給人家看,一絲不掛。

問題分析:__return_true 的萬惡之源

這次被標記的漏洞屬於**「資訊洩漏(Information Exposure)」**。

在開發 REST API Endpoint 時,為了測試方便,我在 permission_callback 中使用了 WordPress 內建的 __return_true 函式:

PHP

register_rest_route( 'site-prober/v1', '/logs', [

'methods' => 'GET',

'callback' => [ $this, 'get_logs' ],

'permission_callback' => '__return_true', // 漏洞所在

]);

這意味著:方便成隨便。任何人只要知道 API 路徑,無需登入即可讀取包含使用者行為、IP 地址等敏感資訊的日誌。這在生產環境中是極大的安全隱患。

解法驗證:從 401 Unauthorized 到 200 OK

修復邏輯看似簡單,但在實務開發環境(localhost)的驗證過程中,我遇到了一些有趣的技術挑戰。

1. 實作正確的權限檢查

我將權限回呼函數改為嚴格的 manage_options 檢查,確保只有具備管理員權限的使用者能存取。

PHP

public function permissions_read() {

return current_user_can( 'manage_options' );

}

2. 解決 Localhost 的認證陷阱

在本地開發環境(XAMPP/Apache)驗證修補程式時,我發現即便使用了 Application Passwords,從瀏覽器讀 API 依然回傳 401 Unauthorized:

JSON

{

"code": "rest_forbidden",

"message": "Sorry, you are not allowed to do that.",

"data": { "status": 401 }

}

None
瀏覽器讀 API 依然回傳 401 Unauthorized

【技術插曲:消失的 Authorization Header】

因為Apache 預設會過濾掉 Authorization Header,導致 WordPress 根本拿不到憑證。我必須在wordpress安裝目錄下的 .htaccess 有底下這兩行,憑證才能順利送進 PHP:

Apache

<IfModule mod_rewrite.c>

RewriteEngine On

RewriteRule .* — [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

為了確保測試無誤,我在開發環境寫了一段測試邏輯來手動解析 Header,確認憑證是真的有被送進來:

PHP

// 開發環境測試:手動解析 Basic Auth Header

$auth_header = $_SERVER['HTTP_AUTHORIZATION'] ?? ''

if ( strpos( $auth_header, 'Basic ' ) === 0 ) {

$credentials = explode( ':', base64_decode( substr( $auth_header, 6 ) ) );

$user_login = $credentials[0];

$password = $credentials[1];

$user = wp_authenticate( $user_login, $password );

if ( ! is_wp_error( $user ) && user_can( $user, 'manage_options' ) ) {

return true;

}

}

隨後使用 fetch 進行腳本測試:

JavaScript

const user = 'ooxxxoo' // 你的登入帳號

const pass = 'ooXX OOxx ooXX OOxx ooXX OOxx' // 24 位 Application Password

const token = btoa(`${user}:${pass}`);

fetch('/wordpress/wp-json/site-prober/v1/logs', {

method: 'GET',

headers: {

'Authorization': `Basic ${token}`,

'Content-Type': 'application/json'

}

})

.then(res => res.json())

.then(data => console.log("API 回傳資料:", data));

None
在瀏覽器開發工具使用 fetch 進行腳本測試

a. 為什麼 fetch 會成功?

因為在程式碼中主動塞入了身分證(Credential)。瀏覽器發送請求時,會帶著 Authorization Header 告訴 WordPress:我是 某某某,這是我的 App Password。WordPress 接收後驗證成功,回傳 200 OK

b. 為什麼直接打網址會失敗 (401)?

因為直接在網址列輸入 URL 時,瀏覽器發起的是一個 匿名且不帶自定義 Header 的 GET 請求。它不會主動幫你編碼帳密。WordPress 沒看到 Header 或 Cookie,判定你是匿名使用者(User ID: 0),根據新權限邏輯,自然回傳 401。

c. 什麼時候直接打網址也會成功?

  • 瀏覽器彈出登入框:伺服器回傳 WWW-Authenticate Header 時。
  • Cookie 驗證:如果你已登入 WP 後台,瀏覽器會自動帶上 Cookie,REST API 也能認出你是管理員。這也是為什麼在 Gutenberg 編輯器中能正常看到資料的原因。

3. 端到端驗證 (End-to-End)

  • 外部存取(未登入):確認回傳 rest_forbidden,成功封鎖未授權請求。
  • 內部存取(Gutenberg):利用 @wordpress/api-fetch 自動帶入 Nonce 的機制,確認管理員在後台仍能流暢讀取數據。

在寄出修補說明信後的 24 小時內,我收到了官方的正面回覆:「The plugin has been re-listed.」

None
The plugin has been re-listed

總結這次處理 CVE 的過程讓我再次體認到:安全沒有捷徑。測試時的暫時方便往往是未來漏洞的源頭。順手列一下近年因測試後門造成的重大安全事件:

  1. 2023 年:iRent(和雲行動服務)會員個資外洩事件

這起事件與本篇的 CVE 案例極其相似,都是因為資料庫/API 處於不設防狀態。

事因:一名國外資安研究員發現 iRent 的一個雲端資料庫(Elasticsearch)完全沒有設定密碼。

關鍵點:這通常是開發或測試階段為了方便存取而暫時關閉驗證,但在部署到正式生產環境時卻忘了開啟。

代價:約 40 萬名會員的姓名、電話、住址、身分證字號,甚至信用卡部分資訊在網路上裸奔多時。

啟示:雲端環境的預設開放是開發者的惡夢(就像很多公司的防火牆預設all-to-all)。方便存取與裸奔僅有一線之隔。

2. 2022 年:內政部戶政資料外洩案(2,300 萬人個資外洩)

雖然此案背景複雜,但分析報告指出這與舊系統介接及權限管控有關。

事因:駭客在論壇兜售全台 2,300 萬人的戶籍資料。後續調查發現,問題出在某些政府機關與內政部介接的舊版 API 或測試節點管控不嚴,導致資料被整組順走。

代價:全台人民隱私門戶大開,引發社會極大恐慌與國安層級的檢討。

啟示:舊系統的測試權限往往是防禦最弱的環節。如果不使用的介接點不關閉,它就是駭客進門的後院。

3. 平衡報導一下國際事件,2023 年:微軟 (Microsoft) 遭俄羅斯駭客組織 Midnight Blizzard 滲透

即便是頂尖科技公司,也會栽在過時的測試環境。

事因:駭客利用密碼噴灑(Password Spraying)攻擊,攻破了一個遺留的非正式生產環境(Legacy non-production test tenant)。

漏洞點:這個測試帳號竟然擁有存取微軟內部伺服器、甚至讀取高階主管郵件的權限。

代價:微軟的高層郵件內容被竊取,資安商譽大打折扣。

啟示:測試環境與生產環境必須徹底隔離(Isolation)。如果測試環境能連通生產資料,那它就是生產環境的一部分。

這個時代光會寫程式已經沒用了,AI工具產能比人類大多了。隨時注意安全,迅速處理危機並從中學習,才能體現一些些工程師的專業價值。

[後記]

我發現wordpress plugin-check並不會檢查到rest-api權限的問題,它看到我有寫權限回呼就放行了,卻沒看出來我回傳的是一個毫無防備的 true。畢竟有些 API(例如公開的產品列表、天氣資訊)確實不需要權限,所以 PCP 不能直接把它判定為錯誤。為了持續防堵這次的安全漏洞,我在CI中加了PHPUnit test case來確認,本以為順手小動作,沒想到又觸發了原本代碼中的一些小問題,主要是plugin-check和PHPUnit的一些檢查,詳細過程等到良辰吉時再發一篇文章(坑一)。這讓我感覺軟體問題(或工程問題)本質上並不是看到產品就結束了,更多的是事後維護,就像汽車也不適出廠就算了,還有後續的定期維護;防火牆或網站也是要不斷打補釘更新授權碼(不是訂閱制,是持續修補漏洞)。想到《人月神話》中關於軟體開發與手工業的關鍵論點:軟體是手工藝,而非工廠自動化。軟體生產的核心是「人」,而非機器生產,每一行程式碼都涉及邏輯設計與創意。無法並行。 軟體開發包含分析、設計、編碼、測試等多個線性階段。就像懷孕,「找九個孕婦無法在一個月內生出一個小孩」,增加人力無法等比例減少時間。雖然現在有AI輔助可以加速過程。但有很多問題是事先沒想到的,所以也不會一開始就跟AI開好規格。