- 逆向行駛 - https://520.be/ -

你的「一鍵登入」按鈕,可能是駭客的「後門鑰匙」:WPMU DEV Dashboard 高危漏洞全解析

🔥 懶人包:
全球擁有超過 35 萬個活躍安裝的 WordPress 管理外掛 WPMU DEV Dashboard,被爆出一個近乎滿分的嚴重漏洞 CVE-2026-76581,CVSS 評分高達 9.8。問題出在它的「Hub SSO 單一登入」機制,兩段驗證程式碼在拼接參數時忘記加分隔符號,導致駭客只要偷換一個欄位的位置,就能騙過系統的簽章檢查,完全不需要帳號密碼,直接以「管理員」身分長驅直入你的網站後台。目前漏洞細節已經公開,強烈建議所有網站管理者立即更新至 5.0.2 以上版本,或暫時關閉 Hub SSO 功能 ⚠️

🌐 前往 WPMU DEV 官網 [1]

[2]


🟦 🟦 🟦

內容目錄

🧐 這個漏洞到底在講什麼?先用一個生活化比喻搞懂

如果你曾經幫學校社團或家裡的小生意架過 WordPress 網站,多半聽過或用過 WPMU DEV Dashboard 這款外掛。它的角色比較像是一間「網站代管公司」的中央遙控器:你可以在 WPMU DEV 雲端控制台(The Hub)裡,把自己管理的所有 WordPress 網站串在一起,統一做效能最佳化、資安掃描、備份,而其中最方便的一項功能,就是「Hub SSO 單一登入」——只要在雲端控制台按一下按鈕,就能直接無縫登入任何一個受管理的網站後台,完全不用再輸入一次帳號密碼 💡

這聽起來很方便對吧?但方便的背後,最近被抓出一個非常致命的破口。我們先別急著看技術名詞,把整套 Hub SSO 機制想像成夜市或大型展覽會場常見的「雙重關卡 VIP 通關系統」,這套系統由兩位守衛分別把關:

  • 外門守衛(第一關,發放通行證):訪客要求入場時,外門守衛會把訪客的「票號(Token)」「目前狀態(State)」「要去的攤位(Redirect)」以及「來自哪個網域(Domain)」這四項資訊,全部黏在一起、中間完全不留任何空格或逗號,然後蓋上一個理論上無法偽造的防偽鋼印(在密碼學裡叫做 HMAC 簽章),交給訪客帶著走。
  • 內門守衛(第二關,驗證通行證):訪客拿著通行證走到內門,內門守衛負責核對鋼印是不是真的。但問題就出在這裡:內門守衛只看「票號」「狀態」「攤位」這三項,同樣把它們黏在一起核對,完全沒有去比對「網域」這一項。

因為兩位守衛在抄寫資訊時,字與字之間完全沒有使用任何分隔符號(這在程式設計上稱為「未分隔的字串拼接」缺陷),駭客就發現了一套堪稱「偷天換日」的欺敵手法:他先故意把「要去的攤位」欄位留白,讓外門守衛蓋章時,實際上等於只把「票號、狀態、網域」黏在一起蓋了合法的章。接著,駭客把剛剛拿到的「網域」資訊,填進內門守衛要檢查的「攤位」欄位裡。因為所有字元緊密相連、沒有分界線,內門守衛重新拼出來的字串跟外門守衛蓋章時的字串在位元組層面上長得一模一樣,驗證因此順利通過 😱

💡 白話文再翻譯一次:這就像你把三個包裹的收件人姓名貼紙全部撕下來、直接黏在一起寄出
假設倉庫收貨員原本要核對「姓名 + 地址 + 電話」三張標籤是否吻合,但因為標籤紙全部黏成一長條、沒有分隔線,有心人只要把「地址」欄位刻意留白,再把別人的「電話」偷偷貼進「地址」的位置,只要黏出來的長條紙條「長度跟排列方式」跟原本一致,收貨員照樣會蓋章放行,完全沒發現包裹早就被偷天換日 🙅‍♂️

透過這種文字遊戲,系統的防偽鋼印機制徹底失效。駭客在完全不知道任何網站管理員密碼的情況下,就能騙過系統,被當成擁有最高權限的「管理員」直接放行,長驅直入網站後台⚠️


🟩 🟩 🟩

📖 事情的來龍去脈:一場跟時間賽跑的通報戰

這起資安事件的處理過程,可說是「資安研究機構 + 開發商緊密協作」的教科書等級範例。整起事件是由知名 WordPress 資安防禦機構 Wordfence 的研究員 Alex Thomas,透過內部研發的安全研究工具「Wordfence Argus」在例行性深度研究中主動挖出,並不是外部通報或事後爆料 🔍

時間節點 具體進展與處置動作
2026-08-19 Wordfence 研究員透過內部工具主動發現漏洞,完成驗證後,透過漏洞管理入口網站私下通報給 WPMU DEV 開發團隊
2026-08-21 WPMU DEV 於兩天內正式確認此通報,並向 Wordfence 提交預先釋出的修補程式進行安全審核
2026-08-24 WPMU DEV 正式對外發布安全的 5.0.2 版本更新
2026-08-25 Wordfence 緊急為 Premium、Care、Response 層級付費客戶部署專屬 WAF 防禦規則
2026-08-27 漏洞細節正式對外公開揭露,並被分配 CVE-2026-76581 編號
2026-09-24(預計) 免費版 Wordfence 用戶將於通報滿 30 天後,獲得相同的防禦規則更新

從發現到官方釋出修補版本,WPMU DEV 只花了短短 5 天,反應速度值得肯定。但也要提醒讀者,截至報告撰寫的 2026 年 8 月 28 日,🟡 資安分析普遍認為,由於漏洞細節已全面公開、且利用門檻極低(不需身分驗證、不需高深技術),全球性自動化掃描與大規模攻擊隨時可能爆發,雖然目前尚未有大規模破壞性攻擊災情傳出,但這不代表可以掉以輕心 🌠

💡 冷知識:Wordfence 為什麼要「延遲 30 天」才給免費用戶 WAF 規則?
這是資安業界常見的作法:如果防禦規則一公開,等於間接告訴駭客「漏洞細節長什麼樣子」,反而可能被拿去逆向工程、加速攻擊武器化的速度。先讓付費客戶優先受到保護,等威脅稍微降溫後,才把規則下放給所有免費用戶,是在「儘速修補」跟「不助長攻擊」之間找平衡 🕯️


🔶 🔶 🔶

⚠️ 對我的網站有什麼實質影響?

此漏洞被評定為最高危險級別,CVSS 9.8,幾乎是滿分。這代表它對網站營運構成毀滅性威脅,若未及時修補,最壞情況會依序出現以下四種災難 🚫

  • 網站全面淪陷(Site Takeover):任何攻擊者,不論技術背景高低,只要發送特定構造的惡意網址請求,就能瞬間在目標網站上取得映射至 Hub SSO 的最高管理員權限。
  • 機密資料外洩與商譽受損:攻擊者取得管理員權限後,可輕易匯出資料庫,包含所有會員個資、電子商務交易紀錄、信用卡後設資料與內部商業機密,這不僅違反 GDPR 等資料保護法規,更會對商譽造成不可逆的打擊。
  • 遠端程式碼執行(RCE)與基礎設施勒索:這是最具破壞性的階段。若伺服器未關閉 WordPress 內建的「外掛與佈景主題編輯器」,或未限制檔案系統寫入權限,攻擊者可直接透過後台寫入惡意 PHP 程式碼(Web Shell),導致整台伺服器淪為駭客的肉雞,被拿去發送垃圾郵件、發動 DDoS、挖礦,甚至遭勒索軟體加密。
  • SEO 毒化與惡意廣告植入:攻擊者也可能選擇不立即破壞網站,而是隱蔽地修改路由邏輯,把搜尋引擎爬蟲導向惡意網站,或在頁面中植入釣魚連結,最終導致網站被 Google 等搜尋引擎列入黑名單。

🚨 畫重點!千萬別踩雷:這個漏洞完全不需要帳號密碼、也不需要任何使用者互動(不用騙你點連結),攻擊者只要能連上你的網站,就能發動攻擊。這代表傳統「密碼設複雜一點就比較安全」的思維在這裡完全無效 🙅‍♂️


🟪 🟪 🟪

🎭 四種使用情境:你的網站到底該怎麼辦?

🙋 一般網站管理者(部落格主、小型商店)

如果你只是自己架個部落格或小型網路商店,平常沒特別去雲端控制台一鍵登入,你需要做的事情很單純:檢查外掛版本、確認自己有沒有連接 WPMU DEV Hub 並啟用 SSO 功能。這是最容易被忽略、卻決定你到底有沒有暴露在風險中的關鍵設定 🌠

適合行動:高優先。登入後台檢查版本,若低於 5.0.2 立刻更新,五分鐘內就能完成,完全不需要程式背景 👍

👨‍💻 獨立開發者/網站代管商(DevOps)

這個族群才是這次漏洞真正的核心受災戶。WPMU DEV Dashboard 本身的設計目的,就是讓代管多個客戶網站的開發者或工作室,在一個雲端控制台裡「一鍵登入」旗下所有網站。這代表如果你手上管理十幾、二十個客戶網站,而且全部都連接了同一個 Hub 帳號並啟用 SSO,這次的漏洞等於是一次性把所有客戶網站的鑰匙孔都對外敞開,風險是以「網站數量」倍增的 ⚠️

適合行動:極高優先。建議透過 WP-CLI 批次檢查所有代管站台的外掛版本,並在確認全部更新完成前,先暫時關閉尚未更新站台的 Hub SSO 功能作為緩衝 🔧

🏢 企業商業用途(電商、會員制網站)

若你的網站涉及會員個資或金流交易,這起漏洞不只是資安問題,更直接牽涉個資法與 GDPR 合規責任。一旦資料外洩被證實,企業面臨的可能不只是商譽受損,還有主管機關的裁罰 🚫

適合行動:極高優先,且需留存證據。除了更新外掛,建議同步啟動資安事件應變流程,比對日誌並保留稽核紀錄,以供後續調查與法遵佐證 ⚠️

🚨 高風險場景:以為自己「規模小、不重要」,其實正好中招

💡 真實情境模擬:小型接案工作室的隱形風險
一間台灣小型接案工作室,同時幫五、六個客戶維護官網,為了方便省時間,全部都串接到同一組 WPMU DEV Hub 帳號,並且都開著 Single Sign-on 圖個方便。工作室主觀認為「客戶網站都不大、應該沒人想攻擊」,卻沒意識到,正是這種「圖方便、全部集中管理」的設定,恰好符合此漏洞被觸發的完美條件——攻擊者只要摸清楚一組 Hub SSO 的簽章邏輯,就能透過參數位移手法,一次威脅到所有掛在同一套系統下的網站,跟網站本身「大不大」完全無關 🆘

⛔️ 這類「因為代管方便而把多個網站集中串接」的工作室或個人接案者,反而是最容易誤判自己「應該沒事」的高風險族群,請務必優先排查旗下所有網站的外掛版本 🙅‍♂️


🟢 🟢 🟢

🛡️ 我該怎麼做?無痛自救指南

面對這個嚴峻威脅,網站管理者請依照以下優先順序,逐步完成自救動作 💡

  • 確認目前運作版本:登入 WordPress 後台(wp-admin),點擊左側導覽選單的「外掛」→「已安裝的外掛」,在列表中尋找 WPMU DEV Dashboard,仔細查看名稱下方標示的版本號碼。
  • 執行緊急升級:若版本號碼顯示為 5.0.1 或更舊(例如 5.0.0、4.11.x),代表網站正處於極度危險的狀態,請立即點擊「立即更新」按鈕,務必升級至已確認安全的 5.0.2 版本或更高。
  • 暫時無法更新時的替代方案:若因企業內部變更管理審核流程無法立刻升級,請進入「WPMU DEV → Settings → General」,找到「Single Sign-on」區塊,將該功能切換為「停用(Disabled)」,直接阻斷攻擊路徑。
  • 尋求專業技術支援:若不熟悉後台操作、不確定是否有客製化衝突,或網站由第三方代管,請立刻將這份分析轉發給代管公司或內部 IT 團隊,並明確標示這是「最高級別資安事件」,要求優先處理。

🫂 新手求助:完全看不懂「後台」「外掛」在哪裡怎麼辦?
如果你連登入 WordPress 後台都不熟悉,最快的方式是直接聯繫當初幫你架站的網頁設計公司或主機代管商,請他們協助確認外掛版本並執行更新。多數台灣主機商都提供技術支援管道,遇到資安緊急事件不用不好意思開口求助,這比自己硬著頭皮亂改設定安全得多 📚

[55]


🟥 🟥 🟥

🔍 技術深潛篇:CVE 底層原理拆解(給 DevOps 看)

在系統架構層面,這個漏洞是個很有教育意義的經典案例,展示了兩段「各自單獨看都不算錯」的驗證邏輯,在交互作用下如何衍生出致命的密碼學缺陷 📊

項目 內容
CVE 編號 CVE-2026-76581
CVSS 3.1 評分 9.8(Critical)
受影響元件 WPMU DEV Dashboard Plugin(Software Slug:wpmudev-updates
受影響版本 所有 <= 5.0.1 版本
安全修補版本 >= 5.0.2
CWE 分類 CWE-347(不當的加密簽章驗證)+ CWE-551(解析與規範化前的授權順序錯誤)

下表呈現該漏洞的 CVSS v3.1 向量解析,顯示其具備極高的可利用性與破壞力 ⚙️

指標 評估說明
攻擊途徑 AV:N 可從網際網路遠端發起攻擊,不需進入內部網路
攻擊複雜度 AC:L 手法單一,僅需構造特定 HTTP 請求即可觸發
所需權限 PR:N 攻擊者無需具備目標系統的任何帳號或權限
使用者互動 UI:N 攻擊過程全自動化,不需要受害者點擊任何連結
機密性/完整性/可用性影響 C:H / I:H / A:H 可存取所有機密資料、任意修改系統設定、導致服務全面停擺

🧑‍🔧 DevOps 小知識:問題出在 HMAC 的「規範化」沒做好
Hub SSO 被實作為兩個 AJAX 動作:wdpsso_step1wdpsso_step2,兩者都掛載在 WPMUDEV_Dashboard_Ajax 類別的 $nopriv_actions 陣列中,代表任何未認證的請求都能觸發它們。authenticate_sso_access_step1() 在生成簽章時,把 tokenstateredirectdomain 四個變數未分隔地直接拼接後送進 HMAC 雜湊函數;但負責驗證的 authenticate_sso_access_step2() 重建簽章字串時,卻完全省略了 domain 欄位,只對前三個變數做同樣的未分隔拼接。兩段邏輯的不一致,加上字串拼接缺乏明確邊界,正是這次「規範化混淆(Canonicalization Confusion)」的根本成因 🔧

🎯 攻擊鏈路:從觸發到接管只要五步

攻擊者利用上述密碼學實作缺陷,把 wdpsso_step1 端點轉化為一個不需要 API 金鑰的「簽章預言機」,完整攻擊鏈如下 🚫

階段 技術動作
❶ 觸發 攻擊者發送請求至 admin-ajax.php?action=wdpsso_step1,並刻意提交空字串作為 redirect 參數的值
❷ 取得合法簽章 伺服器將空字串代入拼接公式,等同簽署了 token+state+domain 的組合,隨即將合法 HMAC 簽章、token、domain 與相關 Cookie 回傳給攻擊者
❸ 參數位移重放 攻擊者向 admin-ajax.php?action=wdpsso_step2 發動重放攻擊,並將剛才取得的 domain 值,填入 step2 預期的 redirect 參數位置
❹ 驗證邏輯崩潰 step2 重建的字串(token+state+被替換成 domain 的 redirect)與 step1 簽署的字串在位元組層面完全一致
❺ 取得最高權限 hash_equals() 比對結果為真,系統清除當前會話,並針對映射的目標使用者呼叫 wp_set_auth_cookie(),攻擊者瞬間取得完整後台管理權限

已確認的影響:攻擊者可完全繞過身分驗證,在不具備帳號密碼或有效 API 金鑰的情況下,劫持任何已啟用 Hub SSO 並映射至管理員帳號的連線網站 💯

🟡 合理推論:取得管理員權限後,攻擊者通常會進一步利用 WordPress 架構特性,透過上傳惡意外掛、修改佈景主題 functions.php,將攻擊升級為遠端程式碼執行,進而植入 Web Shell 🌠

🔴 假設情境:若目標伺服器權限隔離配置不當(例如 Web Server 以 root 權限運行),Web Shell 的植入可能導致整個基礎設施淪陷,甚至成為攻擊內部網路其他節點的跳板 ⚠️

📊 與兩週前另一起漏洞的比較

值得注意的是,WPMU DEV Dashboard 在本次事件僅兩週前的 2026 年 8 月初,才剛修補過另一個重大認證繞過漏洞 CVE-2026-15459。以下表格對比兩者差異,有助於資安團隊釐清排查方向 🔍

特性比較 本次漏洞(CVE-2026-76581) 前次漏洞(CVE-2026-15459)
CVSS 評分 9.8(Critical) 8.1(High)
受影響情境 網站已連接 WPMU DEV Hub 且啟用 SSO 網站未連接 Hub(新安裝的預設狀態)
根本原因 HMAC 規範化混淆(未分隔拼接與欄位不一致) API 金鑰為空值,導致簽章極易偽造,且缺乏權限檢查
攻擊機制 參數位移與簽章預言機重放 利用空密鑰直接構造偽造的驗證雜湊
修補版本 5.0.2 版修復 5.0.1 版修復

從架構設計角度來看,WPMU DEV Dashboard 採用的自訂 HMAC 流程,相較於業界標準的 OAuth 2.0、OpenID Connect 或 JSON Web Tokens,在實作上更容易出現這類邊界處理的盲點,因為業界標準協定通常強制要求嚴格的欄位序列化與分隔機制,能從根源避免規範化攻擊。🟡 合理推論:這反映出自行實作 SSO 密碼學協定的普遍風險,而非單一外掛的個案問題——近期另一款熱門 WordPress 外掛 miniOrange SAML 2.0 SSO 也才剛爆發過演算法混淆導致的認證繞過漏洞(CVE-2026-15013CVE-2026-15981),突顯了整個 WordPress 生態系在自架 SSO 機制上的共通盲點 🌠


🟧 🟧 🟧

✅ 資安排查與自我檢查 Checklist:先別急著恐慌,把現場一層一層排查清楚

🤖 重要提醒:以下指令與排查流程由 Gemini 產生、Claude 複查
雖然已盡力對照常見實務經驗與 WP-CLI 官方文件,但仍可能因伺服器環境、權限設定或外掛版本差異而產生疏漏。正式環境(尤其是生產站)的高風險操作——包含更新、停用、刪除檔案、變更權限或執行批次腳本——請務必由熟悉該站架構的專業人員進行最後確認與執行,切勿直接複製貼上後就按下 Enter 🧙

CVE-2026-76581 這起漏洞,攻擊者不需要帳號密碼、也不需要你點任何連結,只要你的網站連接了 WPMU DEV Hub 且開啟了 SSO,理論上就已經在攻擊面裡了。所以這一節的排查邏輯不是「看到還沒被入侵就放心」,而是依照 環境判斷 👉 盤點 👉 Log 排查 👉 後門與帳號 👉 排程與加固 的順序,把痕跡一層層縮小 🩺

🔥 一句話先記住:版本升級到 5.0.2 以上,只代表「這個洞已經補起來」,不代表「這個洞從來沒被人踩過」。如果你的網站曾經在 2026 年 8 月 24 日以前,就已經連接 Hub 並開啟 SSO,版本修補跟入侵排查應該當成兩件不同的事來處理 🚷

🧭 ㊀ 先判斷環境:你在顧一個網站,還是整片代管的網站海?

這步驟看起來多餘,但後面所有批次指令能不能安全下手,全靠這一步先分清楚 📔

環境 你看到的結構 正確思路 常用 WP-CLI 定位方式
💠 單一網站 一個 WordPress 安裝 直接在該網站目錄操作 –path=”$WP_PATH”
❇️ 獨立多站 一台伺服器上有多個完全獨立的 WordPress 每個 wp-config.php 代表一個獨立站台,各自可能連到不同 Hub 帳號 –path=”$path”
✴️ Multisite 一個 Network,底下有多個 Site WPMU DEV Dashboard 通常是 Network Activated,Plugin 檔案只有一份 –url=”$url”

💠 單一網站:先確認 WP-CLI 真的站在 WordPress 裡

決策原理:在動任何批次指令前,先確認目前路徑真的能載入 WordPress,比直接對著錯的目錄狂打指令安全太多 🌠

※下方 /var/www 請自行替換為你網站的真實路徑。

WP_PATH="/var/www/example.com/public_html"

if wp --path="$WP_PATH" core is-installed; then
    echo "OK: WordPress installation detected."
else
    echo "ERROR: WordPress installation not detected."
fi

預期輸出:

OK: WordPress installation detected.

分支判斷:

  • ✔️ 顯示 OK 👉 可以進入下一步盤點。
  • 🟠 顯示 ERROR 👉 先停下來,不要把錯誤目錄當網站處理。
  • 🟡 路徑含空格或特殊字元 👉 保留 “$WP_PATH” 的雙引號,不要自己拿掉。

❇️ 獨立多站:先找出每一個真正的 WordPress

決策原理:同一台主機上有很多 WordPress,不代表是 Multisite;應該從各站的 wp-config.php 建立完整 Inventory,才不會漏掉某個客戶網站 📜

SEARCH_ROOT="/var/www"

find "$SEARCH_ROOT" -type f -name "wp-config.php" -print0 |
while IFS= read -r -d '' config; do
    path="$(dirname -- "$config")"

    if wp --path="$path" core is-installed 2>/dev/null; then
        printf 'WordPress: %s\n' "$path"
    fi
done

預期輸出:

WordPress: /var/www/site-a/public_html
WordPress: /var/www/site-b/public_html
WordPress: /var/www/site-c/public_html

分支判斷:

  • ✔️ 每個路徑都通過 👉 建立獨立多站 Inventory,準備逐站排查。
  • 🟠 找到 wp-config.php 卻無法載入 WordPress 👉 先確認 PHP 版本或檔案權限,不要直接更新。
  • 🟡 這批網站裡,哪幾個曾經串接同一組 Hub 帳號?這點務必人工確認,因為 Hub SSO 是「以帳號為單位」暴露風險,不是以單一網站為單位。

✴️ Multisite:把 Network 裡的 Site 列出來

WP_PATH="/var/www/network/public_html"

if wp --path="$WP_PATH" core is-installed; then
    wp --path="$WP_PATH" site list \
        --fields=blog_id,url \
        --format=table
else
    echo "ERROR: WordPress installation not detected."
fi

預期輸出:

blog_id  url
1        https://example.com/
2        https://shop.example.com/
3        https://blog.example.com/

分支判斷:能列出多個 Site 👉 進入 Multisite 流程處理;後續盤點以 Network 為主,Site 層再逐一驗證登入狀態 🎓

🧑‍🔧 DevOps 小知識:WPMU DEV Dashboard 的角色是「整個伺服器的遙控器」,多半以 –network 方式啟用;因此排查時千萬別以為只檢查其中一個 Site 就等於檢查完了整個 Network 🙅


🔎 🔎 🔎

🔎 ㊁ 盤點:wpmudev-updates 版本到底停在哪一版?

公開資料明確指出 5.0.1 以下全數受影響,5.0.2 為修補版本 📊

🚨 畫重點!千萬別踩雷:這個外掛不在 WordPress.org 官方外掛庫,而是透過 WPMU DEV 自己的 Hub/Composer 通道發佈。這代表待會第 5 節你會看到的「Checksum 官方比對」在這裡用不了,必須改用本地雜湊值比對,這點請先記住 ✍️

💠 單一網站:讓 WP-CLI 回報版本,不要自己 grep PHP 猜

決策原理:Plugin Inventory 應以 WordPress 認得的 Plugin metadata 為主,這對本地端安裝的外掛同樣適用,不受它是否上架 WordPress.org 影響 🎓

WP_PATH="/var/www/example.com/public_html"

wp --path="$WP_PATH" plugin get "wpmudev-updates" \
    --fields=name,status,version,update,update_version

預期輸出:

name: wpmudev-updates
status: active
version: 5.0.1
update: available
update_version: 5.0.2

分支判斷:

  • 🔴 5.0.1 或更舊 👉 視為受影響,直接進入第 4 節的應急/修補流程。
  • 🟢 5.0.2 或更新 👉 版本條件已解除,但若此站曾長期暴露在舊版狀態,仍建議完成後續入侵排查。
  • 🟡 update 欄位顯示不出結果 👉 這是正常現象,因為它的更新資訊來自外掛自己註冊到 WordPress 核心更新機制的通道,而非 WordPress.org API;請直接以 version 欄位判斷,不要因為看不到 update 欄位就誤判「沒有更新」。

❇️ 獨立多站:逐站查,同時記下「誰連了 Hub、誰開了 SSO」

決策原理:獨立多站的風險是「以帳號為單位」擴散的——同一個接案工作室常常把所有客戶站串到同一組 Hub 帳號,因此逐站盤點時,版本與連線狀態要一起記錄 🌠

SEARCH_ROOT="/var/www"

find "$SEARCH_ROOT" -type f -name "wp-config.php" -print0 |
while IFS= read -r -d '' config; do
    path="$(dirname -- "$config")"

    if ! wp --path="$path" core is-installed 2>/dev/null; then
        continue
    fi

    version="$(wp --path="$path" \
        plugin get "wpmudev-updates" \
        --field=version 2>/dev/null || true)"

    if [ -n "$version" ]; then
        printf 'Site: %s | WPMU DEV Dashboard: %s\n' "$path" "$version"
    fi
done

預期輸出:

Site: /var/www/site-a/public_html | WPMU DEV Dashboard: 5.0.2
Site: /var/www/site-b/public_html | WPMU DEV Dashboard: 5.0.1
Site: /var/www/site-c/public_html | WPMU DEV Dashboard: 4.11.3

分支判斷:這份清單就是你的第一張風險地圖——B、C 站進入修補名單;就算 A 站版本安全,只要曾經有一段時間是舊版,也建議一併排查 👍

✴️ Multisite:Plugin 檔案只盤點一次,Site 層再確認狀態

WP_PATH="/var/www/network/public_html"

wp --path="$WP_PATH" plugin get "wpmudev-updates" \
    --fields=name,status,version,update,update_version

wp --path="$WP_PATH" plugin is-active "wpmudev-updates" --network
    && echo "wpmudev-updates is Network Activated." \
    || echo "wpmudev-updates is not Network Activated."

分支判斷:Plugin 版本以 Network 安裝檔案為準;若確認為 Network Activated,代表任一 Site 暴露的風險等同整個 Network 暴露 🚫


🧾 🧾 🧾

🧾 ㊂ Log 排查:不是看到 admin-ajax.php 就緊張,而是揪出「先騙票、再搶跑」的節奏

這個漏洞的攻擊路徑走的是 /wp-admin/admin-ajax.php,不是 REST API。真正有鑑識價值的,不是隨便一筆 action=wdpsso_step1action=wdpsso_step2 的請求,而是「同一來源、極短時間內、先呼叫 step1 且 redirect 留空,緊接著呼叫 step2 且 redirect 被填入網域字串」這一整套組合動作 📊

💠 單一網站

LOG_FILE="/var/log/apache2/access.log"

if [ -f "$LOG_FILE" ]; then
    grep -Ei \
        'action=wdpsso_step1|action=wdpsso_step2' \
        "$LOG_FILE" |
    tail -n 150
else
    echo "LOG NOT FOUND: $LOG_FILE"
fi

預期輸出(若有命中):

... "POST /wp-admin/admin-ajax.php?action=wdpsso_step1&redirect= HTTP/1.1" 200 ...
... "POST /wp-admin/admin-ajax.php?action=wdpsso_step2&redirect=example.com HTTP/1.1" 200 ...

分支判斷:

  • 🟢 只有零星單筆 step1 或 step2 請求,沒有前後呼應 👉 優先度較低,持續觀察。
  • 🟡 出現「step1(redirect 為空)緊接著同一 IP 的 step2(redirect=網域字串)」👉 高度符合此攻擊特徵,立刻進入 A 情境(已中招/疑似中招)。
  • 🔴 上述模式出現後,緊接著看到後台登入紀錄或新管理員帳號 👉 直接視為已中招處理,不要再等更多證據。

❇️ 獨立多站

LOG_ROOT="/var/log"

find "$LOG_ROOT" -type f \
    \( -name "*access*.log" -o -name "*access*.log.*" \) \
    -print0 |
while IFS= read -r -d '' log; do
    matches="$(grep -Ei \
        'action=wdpsso_step1|action=wdpsso_step2' \
        "$log" 2>/dev/null || true)"

    if [ -n "$matches" ]; then
        printf '\n===== %s =====\n' "$log"
        printf '%s\n' "$matches" | tail -n 80
    fi
done

分支判斷:只會列出有命中的 Log,方便集中火力調查真正需要留意的站;沒命中不代表絕對安全,Log 可能已輪替或遺失 🧯

✴️ Multisite

LOG_FILE="/var/log/apache2/access.log"

if [ -f "$LOG_FILE" ]; then
    grep -Ei \
        'action=wdpsso_step1|action=wdpsso_step2' \
        "$LOG_FILE" |
    tail -n 150
fi

分支判斷:找到可疑時間點後,搭配 wp site list 的 Site 清單,以 Host/URL 對照出真正受影響的 Site,不要只看 Plugin 是否啟用 📔

🧑‍🔧 DevOps 小知識:公開資安通報指出 Wordfence 已為此漏洞部署 WAF-RULE-868WAF-RULE-916 兩條防禦規則。若你的主機有安裝 Wordfence(尤其是 Premium/Care/Response 層級),建議直接調閱這兩條規則的觸發紀錄,會比自己土法煉鋼 grep 更快找到異常來源 IP ⚙️


👤 👤 👤

👤 ㊃ 後門與帳號排查:管理員名單是不是憑空多了一個?

這個漏洞一旦成功利用,攻擊者拿到的就是「管理員」等級的登入會話,因此帳號、uploads 目錄與外掛/佈景主題清單,都值得一起檢查 🕵️

💡 威脅情資補充(單一來源,僅供參考):目前已有資安部落格回報,鎖定此漏洞的攻擊活動疑似出現特定的 Web Shell 簽章字串,以及帶有「developer-」「starter-toolkit-」等命名慣例的偽裝外掛/佈景主題。🟡 這項細節目前僅見於單一公開來源,尚未有第二個獨立來源交叉驗證,因此僅供排查參考、不宜視為唯一判斷依據;發現符合命名慣例但你自己沒有安裝過的外掛/佈景主題,都應優先列為高度可疑 ❓

💠 單一網站:列出管理員,順手掃 uploads

WP_PATH="/var/www/example.com/public_html"

wp --path="$WP_PATH" user list \
    --role=administrator \
    --fields=ID,user_login,user_email,display_name,user_registered \
    --format=table

wp --path="$WP_PATH" plugin list --format=table
wp --path="$WP_PATH" theme list --format=table

find "$WP_PATH/wp-content/uploads" -type f \
    \( -iname "*.php" -o -iname "*.phtml" -o -iname "*.php*" \) \
    -print 2>/dev/null

預期輸出:

ID  user_login  user_email          display_name       user_registered
1   admin       admin@example.com    Site Admin         2026-01-10 08:30:00
9   svc_backup  ops@example.com      Backup Service      2026-08-25 03:14:02

分支判斷:

  • ✔️ 帳號、外掛、佈景主題清單都能對得上你自己的維運紀錄 👉 這一項沒有異常。
  • 🟠 出現在漏洞公開後(2026-08-27 之後)才建立的管理員帳號 👉 高度可疑,優先進入事件應變。
  • 🔴 uploads 出現不屬於你部署流程的 PHP/可執行檔 👉 先別刪,保留時間戳、權限與雜湊值供鑑識。

❇️ 獨立多站

SEARCH_ROOT="/var/www"

find "$SEARCH_ROOT" -type f -name "wp-config.php" -print0 |
while IFS= read -r -d '' config; do
    path="$(dirname -- "$config")"

    if ! wp --path="$path" core is-installed 2>/dev/null; then
        continue
    fi

    printf '\n===== %s =====\n' "$path"

    wp --path="$path" user list \
        --role=administrator \
        --fields=ID,user_login,user_email,display_name,user_registered \
        --format=table
done

分支判斷:某站出現未知管理員 👉 優先標記該站進行事件調查;不要因為其他站看起來乾淨就整批放過 🚨

✴️ Multisite

WP_PATH="/var/www/network/public_html"

wp --path="$WP_PATH" user list \
    --fields=ID,user_login,user_email,display_name,user_registered \
    --format=table

分支判斷:發現未知帳號後,需進一步確認它擁有的是 Network 層還是 Site 層權限,不要看到帳號存在就直接刪除 🙅


⏰ ⏰ ⏰

⏰ ㊄ 排程與加固檢查:Cron 有沒有被塞東西、後門有沒有留第二道路

💠 單一網站

WP_PATH="/var/www/example.com/public_html"

wp --path="$WP_PATH" cron event list \
    --fields=hook,next_run_gmt,recurrence \
    --format=table

wp --path="$WP_PATH" config get DISALLOW_FILE_EDIT 2>/dev/null || echo "未設定"
wp --path="$WP_PATH" config get DISALLOW_FILE_MODS 2>/dev/null || echo "未設定"

分支判斷:

  • 🟠 出現未知 Hook 👉 先查它來自哪個 Plugin/Theme,不要看到名稱奇怪就直接刪除。
  • 🟡 DISALLOW_FILE_EDITDISALLOW_FILE_MODS 顯示「未設定」👉 代表後台的外掛/佈景主題編輯器目前是開著的,這是第 4 節加固步驟要優先補上的項目。

❇️ 獨立多站

SEARCH_ROOT="/var/www"

find "$SEARCH_ROOT" -type f -name "wp-config.php" -print0 |
while IFS= read -r -d '' config; do
    path="$(dirname -- "$config")"

    if ! wp --path="$path" core is-installed 2>/dev/null; then
        continue
    fi

    printf '\n===== CRON/加固檢查: %s =====\n' "$path"

    wp --path="$path" cron event list \
        --fields=hook,next_run_gmt,recurrence \
        --format=table

    wp --path="$path" config get DISALLOW_FILE_EDIT 2>/dev/null || echo "未設定"
done

✴️ Multisite

WP_PATH="/var/www/network/public_html"

wp --path="$WP_PATH" cron event list \
    --fields=hook,next_run_gmt,recurrence \
    --format=table

如果發現可疑事件,搭配 Site URL 與前面的 Log 時間線一起判斷,不要單獨看 Cron 就下結論 🎓


🛟 🛟 🛟

🛟 真的出事了怎麼辦——從止血到正式回歸

階段 目的 核心動作
A. 已中招/疑似中招 先留證再處理,避免證據隨手被清掉 建立事件目錄、隔離、通報
B. 短期應急 縮短暴露時間,不等於修好 停用外掛/關閉 SSO/WAF 阻擋
C. 正式修補 徹底關閉漏洞並確認乾淨 備份 👉 Dry-run 👉 更新 👉 驗證 👉 加固

🔥 這裡最重要的閉環:判斷環境 👉 盤點 👉 備份 👉 Dry-run 👉 更新 👉 驗證。更新成功不等於事件結束,尤其這次漏洞已經有攻擊活動被觀察到,寧可多花十分鐘覆查,也不要邊做邊祈禱 🙏

🚨 A. 已中招/疑似中招:先留證,再談修補

💠 單一網站

決策原理:疑似入侵時,最大的敵人不是漏洞本身,而是還沒留下證據就急著清理 ❤️‍🩹

WP_PATH="/var/www/example.com/public_html"
INCIDENT_DIR="$HOME/wpmudev-incident-$(date +%Y%m%d_%H%M%S)"

mkdir -p "$INCIDENT_DIR"

{
    echo "=== Incident collection ==="
    date -Is
    echo "WP_PATH=$WP_PATH"

    wp --path="$WP_PATH" core version
    wp --path="$WP_PATH" plugin get "wpmudev-updates" \
        --fields=name,status,version,update,update_version

    wp --path="$WP_PATH" user list \
        --role=administrator \
        --fields=ID,user_login,user_email,user_registered \
        --format=csv
} > "$INCIDENT_DIR/summary.txt" 2>&1

echo "Incident evidence: $INCIDENT_DIR"

預期輸出:

Incident evidence: /home/admin/wpmudev-incident-20260904_121500

分支判斷:證據建立完成 👉 進入止血;若是電商或會員制網站,同步通知內部資安或維運負責人,並保留這份紀錄供事後法遵佐證 📔

❇️ 獨立多站:只隔離有證據的站,不要整台伺服器一起誤傷

INCIDENT_PATH="/var/www/site-b/public_html"

if wp --path="$INCIDENT_PATH" core is-installed; then
    echo "Incident target confirmed: $INCIDENT_PATH"
else
    echo "ERROR: target is not a WordPress installation."
fi

分支判斷:確認路徑正確 👉 針對該站執行後續應急;如果多個客戶站都掛在同一組 Hub 帳號下,務必逐站盤點,因為風險是跟著帳號走的,不是跟著單一網站走 ⚠️

✴️ Multisite:先當成 Network 級事件處理

WP_PATH="/var/www/network/public_html"

wp --path="$WP_PATH" site list \
    --fields=blog_id,url \
    --format=table

wp --path="$WP_PATH" plugin get "wpmudev-updates" \
    --fields=name,status,version,update,update_version

分支判斷:任一 Site 出現明確入侵跡象 👉 把整個 Network 都納入事件調查範圍,不要只處理出事的那一個 Site 🚫


🧯 🧯 🧯

🧯 B. 尚未確認中招,但也還不能立刻升級:先做短期應急

🚨 短期應急 ≠ 正式修補。停用外掛、關閉 SSO、WAF 阻擋都只是緩衝措施;真正的修補仍然是升級到 5.0.2 以上 🦺

💠 單一網站:能停用就先停用外掛,把攻擊面直接關掉

WP_PATH="/var/www/example.com/public_html"

wp --path="$WP_PATH" plugin deactivate "wpmudev-updates"

預期輸出:

Plugin 'wpmudev-updates' deactivated.
Success: Deactivated 1 of 1 plugins.

分支判斷:

  • 🟢 業務不依賴 Hub 遠端管理功能 👉 保留停用狀態,盡快排入正式修補。
  • 🟠 網站仍需要 WPMU DEV 的效能/備份功能,無法直接停用 👉 改由後台「WPMU DEV → Settings → General」把 Single Sign-on 切成停用,僅關閉 SSO 這一項功能。
  • 🟡 不確定該站確切的 Hub SSO 開關選項鍵值 👉 可先用 wp option list –search=”*wpmudev*” 找出相關選項名稱,再交叉比對後台介面,避免用猜測的 option 名稱直接改資料庫。

❇️ 獨立多站:逐站應急,不要一次停掉整台伺服器的所有外掛

INCIDENT_PATH="/var/www/site-b/public_html"

wp --path="$INCIDENT_PATH" plugin deactivate "wpmudev-updates"

分支判斷:只影響 Site B,不應該連 Site A、Site C 一起停掉;每一站的業務依賴程度可能不同 🌠

✴️ Multisite:先確認是否為 Network Activated 再決定範圍

WP_PATH="/var/www/network/public_html"

wp --path="$WP_PATH" plugin is-active "wpmudev-updates" --network \
    && echo "wpmudev-updates is Network Activated." \
    || echo "wpmudev-updates is not Network Activated."

分支判斷:若為 Network Activated,停用會影響整個 Network,執行前務必先跟相關 Site 負責人確認,不要單方面直接停用 🙅

🧑‍🔧 DevOps 小知識:如果現階段完全不能停用外掛(例如高度依賴 Hub 的自動備份排程),可在邊界防禦層(Cloudflare、ModSecurity、AWS WAF)針對 /wp-admin/admin-ajax.php 且 Query String 命中 action=wdpsso_step1action=wdpsso_step2 的請求做暫時阻擋,但務必先在 Staging 驗證規則,避免誤擋合法的 Hub 連線 ⚙️


🚀 🚀 🚀

🚀 C. 正式修補:備份 👉 Dry-run 👉 更新 👉 驗證 👉 加固

💾 備份:先留一條回家的路

決策原理:外掛更新主要動到 Plugin 檔案,但資料庫與媒體檔案仍可能需要救援,因此至少要有資料庫備份 ❤️‍🩹

# 💠 單一網站
WP_PATH="/var/www/example.com/public_html"
BACKUP_DIR="$HOME/wp-security-backup"
STAMP="$(date +%Y%m%d_%H%M%S)"

mkdir -p "$BACKUP_DIR"

wp --path="$WP_PATH" db export \
    "$BACKUP_DIR/example-com_pre_wpmudev_update_${STAMP}.sql"

tar -czf \
    "$BACKUP_DIR/example-com_wp-content_pre_wpmudev_update_${STAMP}.tar.gz" \
    -C "$WP_PATH" \
    "wp-content"
# ❇️ 獨立多站:每個 WordPress 各自一份,不要混在一起
SEARCH_ROOT="/var/www"
BACKUP_ROOT="$HOME/wp-security-backup"
STAMP="$(date +%Y%m%d_%H%M%S)"

mkdir -p "$BACKUP_ROOT"

find "$SEARCH_ROOT" -type f -name "wp-config.php" -print0 |
while IFS= read -r -d '' config; do
    path="$(dirname -- "$config")"

    if ! wp --path="$path" core is-installed 2>/dev/null; then
        continue
    fi

    site_name="$(basename "$path")"

    wp --path="$path" db export \
        "$BACKUP_ROOT/${site_name}_pre_wpmudev_update_${STAMP}.sql"
done
# ✴️ Multisite:Network Database 只 dump 一次
WP_PATH="/var/www/network/public_html"
BACKUP_DIR="$HOME/wp-security-backup"
STAMP="$(date +%Y%m%d_%H%M%S)"

mkdir -p "$BACKUP_DIR"

wp --path="$WP_PATH" db export \
    "$BACKUP_DIR/multisite_pre_wpmudev_update_${STAMP}.sql"

分支判斷:備份都成功 👉 進 Dry-run;任一環境備份失敗 👉 停止,不進入正式更新 💯


🧪 🧪 🧪

🧪 Dry-run:讓 WP-CLI 先講它準備更新什麼

# 💠 單一網站
WP_PATH="/var/www/example.com/public_html"

wp --path="$WP_PATH" plugin update \
    "wpmudev-updates" \
    --version=5.0.2 \
    --dry-run

預期輸出:

Plugin wpmudev-updates 5.0.1 will be updated to 5.0.2.
# ❇️ 獨立多站:每站各自 Dry-run
SEARCH_ROOT="/var/www"

find "$SEARCH_ROOT" -type f -name "wp-config.php" -print0 |
while IFS= read -r -d '' config; do
    path="$(dirname -- "$config")"

    if ! wp --path="$path" core is-installed 2>/dev/null; then
        continue
    fi

    version="$(wp --path="$path" \
        plugin get "wpmudev-updates" \
        --field=version 2>/dev/null || true)"

    if [ -n "$version" ]; then
        printf '\n===== %s | current=%s =====\n' "$path" "$version"

        wp --path="$path" plugin update \
            "wpmudev-updates" \
            --version=5.0.2 \
            --dry-run
    fi
done
# ✴️ Multisite:Plugin 檔案只 Dry-run 一次
WP_PATH="/var/www/network/public_html"

wp --path="$WP_PATH" plugin update \
    "wpmudev-updates" \
    --version=5.0.2 \
    --dry-run

分支判斷:看到目標版本 5.0.2 👉 可以正式更新;🟡 由於這個外掛不是 WordPress.org 上架外掛,它的更新來源是自家 Hub/Composer 通道,若 Dry-run 顯示「沒有可用更新」,請先確認該站是否已正確連接 Hub 帳號,而不是直接認定「已經安全」🔍


🔵 🔵 🔵

🔵 正式更新:一站更新、一站驗證

# 💠 單一網站
WP_PATH="/var/www/example.com/public_html"

wp --path="$WP_PATH" plugin update \
    "wpmudev-updates" \
    --version=5.0.2

預期輸出:

Updating 'wpmudev-updates'...
Plugin updated successfully.
Success: Updated 1 of 1 plugins.
# ❇️ 獨立多站:一站一個完整閉環,不要全部更新完才檢查
WP_PATH="/var/www/site-a/public_html"

wp --path="$WP_PATH" plugin update \
    "wpmudev-updates" \
    --version=5.0.2

wp --path="$WP_PATH" plugin get \
    "wpmudev-updates" \
    --fields=name,status,version
# ✴️ Multisite:Plugin 檔案更新一次,再逐 Site 驗證
WP_PATH="/var/www/network/public_html"

wp --path="$WP_PATH" plugin update \
    "wpmudev-updates" \
    --version=5.0.2

wp --path="$WP_PATH" site list --field=url |
while IFS= read -r url; do
    printf '\n===== %s =====\n' "$url"

    wp --path="$WP_PATH" \
        --url="$url" \
        plugin get "wpmudev-updates" \
        --fields=name,status,version
done

分支判斷:更新成功 👉 立即進版本驗證;更新失敗 👉 不要反覆重試,先查權限、金鑰或 Hub 連線狀態再動作 ⭕


🔐 🔐 🔐

🔐 完整性驗證:官方 Checksum 用不了,改用本地雜湊比對

決策原理:如前面已提醒,wp plugin verify-checksums 依賴 WordPress.org 提供的 checksum 資料庫,對 wpmudev-updates 這種未上架的商業外掛會直接失敗,因此改用「同版本雜湊比對」:從 Hub 官方通道重新下載一份乾淨的同版本外掛,跟現有安裝逐檔案比對 SHA-256 值 📔

# 💠 單一網站:抽查關鍵目錄的雜湊值
WP_PATH="/var/www/example.com/public_html"
PLUGIN_DIR="$WP_PATH/wp-content/plugins/wpmudev-updates"

find "$PLUGIN_DIR" -type f -name "*.php" -print0 |
xargs -0 sha256sum \
    > "$HOME/wpmudev-updates_current_hashes.txt"

echo "已輸出現有安裝雜湊清單,請與另行下載的官方 5.0.2 版本比對。"

預期輸出:一份逐檔案 SHA-256 清單。此清單本身不會告訴你「有沒有被改過」,必須另外拿一份確定乾淨的同版本安裝檔,逐行比對雜湊值是否一致,這是這個外掛沒有官方 Checksum API 時最務實的替代方案 💡

分支判斷:

  • 🟢 雜湊值全數一致 👉 外掛檔案完整性沒問題。
  • 🔴 有檔案雜湊值兜不起來 👉 不要忽略,這代表有人動過檔案,應立即回到 A 情境(已中招/疑似中招)流程重新處理。
  • ❓ 沒有管道取得另一份「確定乾淨」的同版本安裝檔 👉 這一項先標示待確認,改以第 3 節的帳號、Log、uploads 排查結果作為主要判斷依據,不要自己腦補一份雜湊清單來比對。
# 💠 順手驗證 WordPress 核心本身(此指令沒有上述限制,因為核心有官方 Checksum)
wp --path="$WP_PATH" core verify-checksums
# ❇️ 獨立多站:逐站抽查
SEARCH_ROOT="/var/www"

find "$SEARCH_ROOT" -type f -name "wp-config.php" -print0 |
while IFS= read -r -d '' config; do
    path="$(dirname -- "$config")"

    if ! wp --path="$path" core is-installed 2>/dev/null; then
        continue
    fi

    printf '\n===== CORE CHECKSUM: %s =====\n' "$path"

    wp --path="$path" core verify-checksums
done
# ✴️ Multisite:Core 只需驗證一次
WP_PATH="/var/www/network/public_html"

wp --path="$WP_PATH" core verify-checksums

🚨 Checksum 不是「無罪證明書」。它只回答「這些檔案跟官方版本比對是否一致」,回答不了 uploads、mu-plugins、資料庫與管理員帳號是否曾遭入侵,這一點跟第 3 節的排查工作是互補、不是取代 🦹‍♂️


🧱 🧱 🧱

🧱 加固:補上第二道門鎖

決策原理:這起漏洞的深層教訓是「就算 SSO 機制本身修好了,也要假設未來還會出現類似的權限提升漏洞」,因此把後台程式碼編輯器與檔案寫入權限一併鎖起來,是很划算的深度防禦 🔒

# 💠 單一網站:在 wp-config.php 加入安全常數
WP_PATH="/var/www/example.com/public_html"
WP_CONFIG="$WP_PATH/wp-config.php"

if ! grep -q "DISALLOW_FILE_EDIT" "$WP_CONFIG"; then
    sed -i "/\/\* That's all, stop editing/i define( 'DISALLOW_FILE_EDIT', true );\ndefine( 'DISALLOW_FILE_MODS', true );" "$WP_CONFIG"
    echo "已加入 DISALLOW_FILE_EDIT/DISALLOW_FILE_MODS。"
else
    echo "已存在,略過。"
fi

wp --path="$WP_PATH" config get DISALLOW_FILE_EDIT

預期輸出:

1

分支判斷:

  • ✔️ 兩個常數都回傳 1 👉 加固完成。
  • 🟠 網站仍需要在後台直接編輯外掛/佈景主題程式碼(例如開發環境)👉 先評估是否真的必要,Production 環境原則上不建議開啟。
# ❇️ 獨立多站:逐站套用同樣的加固邏輯
SEARCH_ROOT="/var/www"

find "$SEARCH_ROOT" -type f -name "wp-config.php" -print0 |
while IFS= read -r -d '' config; do
    path="$(dirname -- "$config")"

    if ! grep -q "DISALLOW_FILE_EDIT" "$config"; then
        sed -i "/\/\* That's all, stop editing/i define( 'DISALLOW_FILE_EDIT', true );\ndefine( 'DISALLOW_FILE_MODS', true );" "$config"
        echo "已加固: $path"
    fi
done
# ✴️ Multisite:Network 只需加固一次
WP_PATH="/var/www/network/public_html"
WP_CONFIG="$WP_PATH/wp-config.php"

if ! grep -q "DISALLOW_FILE_EDIT" "$WP_CONFIG"; then
    sed -i "/\/\* That's all, stop editing/i define( 'DISALLOW_FILE_EDIT', true );\ndefine( 'DISALLOW_FILE_MODS', true );" "$WP_CONFIG"
fi

🩵 荷包試算:升級外掛跟加上這兩行常數都不用花一毛錢,十分鐘內能搞定;但如果拖到被入侵後才處理,資安鑑識、清除後門、重建被 Google 標紅的 SEO 流量,成本往往是「立刻處理」的數十倍以上,這筆帳怎麼算都划不來 💸


🧪 🧪 🧪

🧪 修補後回歸測試與最後複查

# 💠 單一網站
WP_PATH="/var/www/example.com/public_html"

wp --path="$WP_PATH" core is-installed
wp --path="$WP_PATH" plugin get "wpmudev-updates" --fields=name,status,version

LOG_FILE="/var/log/apache2/access.log"
if [ -f "$LOG_FILE" ]; then
    grep -Ei 'action=wdpsso_step1|action=wdpsso_step2' "$LOG_FILE" | tail -n 50
fi

預期結果:

  • 🏮 WordPress 前台、後台皆可正常載入。
  • 🏮 版本顯示 5.0.2 或更新版本。
  • 🏮 Hub 連線與其他 WPMU DEV 功能(備份、效能)正常運作。
  • 🏮 更新後 Log 沒有再出現 step1/step2 的可疑組合。
# ❇️ 獨立多站:一站一驗證,逐站推進
WP_PATH="/var/www/site-a/public_html"

wp --path="$WP_PATH" core is-installed
wp --path="$WP_PATH" plugin get "wpmudev-updates" --fields=name,status,version
# ✴️ Multisite:Network 驗證後,逐 Site 測試
WP_PATH="/var/www/network/public_html"

wp --path="$WP_PATH" site list --field=url |
while IFS= read -r url; do
    printf '\n===== VERIFY SITE: %s =====\n' "$url"

    wp --path="$WP_PATH" --url="$url" core is-installed
done

分支判斷:任一站點出現錯誤 👉 暫停批次流程,先處理該站,不要繼續擴大變更範圍 🚫

完成標準:不是 WP-CLI 顯示 Updated successfully 就算收工,而是版本跨過 5.0.2、備份可用、Core Checksum 正常、雜湊比對沒有異常、Log 與帳號沒有可疑紀錄,前台與後台功能都正常,才是真正把這道門鎖好 👌


🏁 🏁 🏁

🏆 事件綜合評估(10 分制,數字越高代表風險或應對品質越正向)

評估維度 分數 一句話評語
漏洞嚴重程度(分數越高代表風險越大) 9.8 近乎滿分的 CVSS 評分,且不需任何前置條件即可遠端觸發,屬於最高危等級
官方應變速度 9.5 從內部主動發現、通報到釋出修補版僅 5 天,反應速度值得肯定
一般站長自救可行性 8 更新版本或暫時停用 SSO 兩步驟即可解決,操作門檻不高
代管業者/DevOps 衝擊規模 7 因 Hub SSO 專為多站代管設計,一旦中招容易一次波及旗下所有客戶網站
DevOps 技術文件完整度 8.5 提供完整攻擊鏈、CVSS 向量與日誌排查特徵,方便進行事後鑑識
台灣代管工作室曝險影響評估(在地化) 7.5 台灣大量中小型接案工作室習慣集中管理多個客戶網站,屬於高曝險族群,需特別留意
🏆 加權綜合建議 立即行動 最終建議:【24 小時內完成更新或停用 SSO】

🔥 CVE-2026-76581 是一個教科書等級的案例,說明「兩段各自看起來都合理的驗證邏輯」,只要在拼接資料時少了一個分隔符號,就足以讓整套密碼學防線形同虛設。它不需要駭客技術高超,只需要你的網站剛好符合兩個條件:外掛版本過舊、且啟用了 Hub SSO 單一登入。這起事件最大的提醒是:方便與安全往往是一體兩面,尤其當漏洞細節已經公開流傳,每拖延一小時,都是把大門鑰匙放在信箱裡等人來拿 🔑

[56]


🛠️ 🛠️ 🛠️

💪 附錄:三分鐘自救 SOP 一覽表

  • ❶ 登入後台「外掛」→「已安裝的外掛」,確認 WPMU DEV Dashboard 版本是否 ≤ 5.0.1
  • ❷ 點擊「立即更新」,確保更新後版本 ≥ 5.0.2
  • ❸ 若暫時無法更新,前往「WPMU DEV → Settings → General」,將 Single Sign-on 功能切換為停用
  • ❹ 若同時代管多個客戶網站,逐一排查所有掛在同一組 Hub 帳號下的站台版本,不要只顧著更新自己主要負責的那一個
  • ❺ 更新完成後,建議比對日誌是否有異常 AJAX 請求序列,並檢查是否有非預期時間創建的高權限帳號

📎 📎 📎

📚 參考資料與延伸資源

列印本文 [63] 👨‍👩‍👧‍👦 26 次瀏覽