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

你網站的「同意 Cookie」按鈕,正在幫駭客開門?WPLP 漏洞全解析(CVE-2026-75865/CVSS 9.8)

🔥 懶人包:
全球超過 10,000 個網站在用的 WordPress 隱私合規外掛 WPLP Cookie Consent(後台顯示名稱為 WP Cookie Notice for GDPR, CCPA & ePrivacy Consent)被爆出嚴重漏洞 CVE-2026-75865,CVSS 評分落在 9.8~10.0 之間,幾乎滿分。問題出在外掛的「上傳商標」功能完全沒有身分驗證、也不檢查檔案類型,等於任何人都能直接把病毒檔案塞進你的網站,而且已經在真實世界被自動化機器人攻擊利用中。更誇張的是,同一個漏洞被兩家資安機構各自登記了一組 CVE 編號。目前官方已釋出 4.4.2 修補版本,強烈建議所有網站管理者立刻更新,並檢查後台是否已經多出了不明的管理員帳號 🆘

🌐 前往官方外掛頁面確認目前版本 [1]


🟦 🟦 🟦

內容目錄

🧐 一個「同意 Cookie」的小外掛,怎麼會變成入侵大門?

先問你一個問題:你的網站上是不是也有那個很煩、每次進站都要點一下「我同意」的 Cookie 提示橫幅?這種功能背後,通常都是靠一款外掛在撐場面,用來幫網站符合歐盟 GDPR、加州 CCPA 這些隱私法規。今天要講的主角 WPLP Cookie Consent,就是這類外掛裡數一數二熱門的一款,全球有超過一萬個網站在用它顯示同意橫幅、擋掉第三方追蹤腳本 🍪

它有一個貼心的加值功能:讓企業可以「上傳自己的商標」,把 Cookie 橫幅換成自家品牌配色。聽起來人畜無害,對吧?問題就出在這個上傳功能背後的那條「後台專用通道」(技術上叫 REST API 端點,路徑名稱是 upload-logo)——它壓根沒在檢查來者是誰 😱

💡 白話文教室:警衛室不見了,包裹檢查機也壞了
把你的網站想像成一棟守備森嚴的企業大樓。「上傳商標」這個功能,理論上應該經過大樓警衛室兩道關卡:第一,警衛要先確認你是不是有識別證的大樓管理員(身分驗證);第二,警衛要拆開你手上的包裹,確認裡面真的是一張圖片,不是別的東西(檔案類型檢查)。

但在這款外掛的設計裡,這兩道關卡同時失守——警衛室空無一人,包裹檢查機也早就壞掉了。結果就是任何路人(未經授權的攻擊者)都能大搖大擺走進大樓,手上還提著一個偽裝成「畫框」的定時炸彈(惡意 PHP 程式碼),直接放進大樓的公共儲藏室(你網站的 wp-content/uploads/ 目錄)。等攻擊者在外面按下遙控器(用瀏覽器打開那個檔案),炸彈就引爆,整棟大樓的鑰匙瞬間換人拿走 🔑


🟢 🟢 🟢

📖 這不是紙上談兵:一場已經在真實世界開打的攻防戰

跟很多「理論上很危險,但目前只是概念驗證」的漏洞不一樣,這次的狀況已經是正在進行式的零日攻擊(Zero-Day Attack)。根據威脅情資與受害者的數位鑑識紀錄,整起事件的時間軸走得非常快,快到讓人有點頭皮發麻 ⏰

時間節點 具體進展與處置動作
2026-08-26 系統管理員透過伺服器日誌與惡意軟體掃描器,捕捉到自動化機器人針對此漏洞發動攻擊,鑑識顯示整條攻擊鏈從探測到上傳後門僅需 20 秒
約 2026-08-26 起 漏洞研究員(含獨立資安專家 Supakiad S. 與社群通報者)透過漏洞獎金計畫向開發商 WPEKA 進行協調揭露
通報後 5 天內 WPEKA 開發團隊完成原始碼重構,釋出安全版本 4.4.2
2026-08-31 Wordfence 與威脅情資平台 IONIX / Patchstack 正式將漏洞登錄至全球漏洞資料庫,發布最高級別資安通報

💡 碎碎念:一個漏洞、兩個 CVE 編號,是怎麼回事?
這次事件有個有趣(但不太重要)的插曲:Wordfence 把這個漏洞登記為 CVE-2026-75865,而 IONIX 與 Patchstack 基於相同的影響版本與成因,另外登記了一組 CVE-2026-82970。這就像同一場車禍,被兩家不同的保險公司各自寫了一份報告,內容講的是同一件事,只是編號不一樣。本文會合併兩者情資,讓你看到完整樣貌,不用糾結該記哪一組編號 🌠

值得留意的是,這起事件是先有真實攻擊、後有官方通報——換句話說,駭客甚至比資安機構更早發現這個洞。這也代表如果你的網站到現在還沒更新,理論上早就已經暴露在風險之中一段時間了,不是「以後可能會被打」,而是「可能現在就已經在挨打」🚨


🔶 🔶 🔶

⚠️ 講白了,這個漏洞能對你的網站做到什麼地步?

從網站營運的角度來看,這個漏洞之所以恐怖,關鍵在於「攻擊門檻趨近於零」加上「破壞力貫穿全站」,是資安圈公認最危險的組合之一 💣

✅ 已確認事實

  • 不需要登入、不需要任何權限:攻擊者不用擁有你網站的任何帳號密碼,就能從遠端直接發動攻擊
  • 可上傳高危險檔案:攻擊者能把惡意的 PHP 後門程式(Dropper)直接寫進網站的上傳資料夾
  • 會建立隱形管理員帳號並竄改時間戳:惡意程式碼執行後,會直接修改 wp_users 資料表,無中生有建立一個 administrator 權限的幽靈帳號,還會把建立時間「回推」到過去,讓它偽裝成舊帳號,躲避你日常巡查

🟡 合理推論

  • 資料外洩與法規裁罰的雙重打擊:駭客取得最高權限後,客戶資料、WooCommerce 交易紀錄、密碼雜湊值都能被輕鬆匯出。最諷刺的地方在於,一個原本用來確保你符合 GDPR 的外掛,反而可能變成讓你違反 GDPR、吃上鉅額罰單的破口
  • SEO 毒化與商譽崩壞:駭客可能植入大量垃圾廣告連結,或把訪客導向詐騙網站,一旦被 Google 列入黑名單,自然流量會瞬間歸零

🔴 假設情境

  • 共享主機骨牌效應:如果你的網站跟其他企業網站共用同一台虛擬主機空間,且沒有啟用 open_basedir 或嚴格的目錄權限限制,攻擊者有機會以你的網站為跳板,橫向入侵同一台伺服器上的其他網站

🚨 畫重點!千萬別踩雷:更新外掛只能阻止未來的攻擊,無法讓「可能已經被寫進伺服器的舊後門檔案」自動消失。如果你在更新前系統就已經被盯上過,光是更新版本並不夠,後面的排查清單章節一定要照著做一遍 🙅‍♂️


🟪 🟪 🟪

🎭 四種情境對應:你到底該多緊張?

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

你不用懂任何程式碼,只需要做兩件事:確認外掛版本、檢查後台有沒有多出你不認識的管理員帳號。這是最容易被忽略、卻決定你到底有沒有暴露在風險中的關鍵動作 🌠

適合行動:極高優先。 立刻更新到 4.4.2 以上,並照著本文後段的「三分鐘自救 SOP」逐項檢查一遍,整個過程不需要任何程式背景 👍

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

如果你手上管理著多個客戶的 WordPress 網站,光靠手動點擊後台效率太低。建議用 WP-CLI 批次檢查所有站台是否安裝了這款外掛及其版本,並優先在伺服器層級(Nginx/Apache)封鎖上傳目錄的 PHP 執行權限,作為修補前的緩衝防線 🔧

適合行動:中高優先。 請直接看後段技術深潛篇的排查指令與應急防堵設定 ⚙️

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

如果你的網站涉及 WooCommerce 交易或大量會員個資,這起漏洞不只是資安問題,更直接牽涉個資法與 GDPR 合規責任。最諷刺的地方在於,你當初裝這款外掛就是為了合規,結果它反而變成違規的破口 🚫

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

🚨 高風險場景:以為跟自己無關,其實正好中招

💡 真實情境模擬:為了服務歐美客戶而裝的合規外掛,反而成了破口
一間台灣的跨境電商,為了服務歐盟與美國加州的客戶,特別裝上這款外掛來處理 GDPR/CCPA 的 Cookie 同意流程,還花時間客製化了自己的品牌商標橫幅。網站主觀認為「我們有裝合規外掛、有基本防火牆,應該算安全」,卻完全沒意識到,正是這個「客製化商標上傳」功能本身,就是攻擊者鎖定的入口。這類「為了國際化合規而主動裝上此外掛」的網站,反而是最容易誤判自己「應該沒事」的高風險族群 🆘

🔴 🔴 🔴

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

在系統架構層面,這次的問題是 WordPress REST API 開發規範的錯誤實作,同時踩到「存取控制」與「檔案處理」兩個地雷 📊

資訊維度 內容詳情
外掛名稱/套件識別碼 WP Cookie Notice for GDPR, CCPA & ePrivacy Consent(Slug:gdpr-cookie-consent
CVE 編號 CVE-2026-75865(Wordfence)/關聯重複編號 CVE-2026-82970(IONIX、Patchstack)
CVSS 評分 9.8(Wordfence)/10.0(IONIX),差異來自對環境要求(AC)與權限範圍(Scope)的微調認定,但都屬於最高級 Critical
CVSS 向量 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H — 可遠端觸發、攻擊複雜度低、不需任何權限與使用者互動,對機密性/完整性/可用性皆造成全面衝擊
CWE 分類 CWE-434(Unrestricted Upload of File with Dangerous Type,未受限制的危險檔案類型上傳)
受影響版本 <= 4.4.1
安全版本 >= 4.4.2

🧑‍🔧 DevOps 小知識:這次不是記憶體漏洞,是兩個安全邊界同時崩塌
WordPress 開發者在用 register_rest_route() 建立自訂 API 端點時,理論上必須嚴謹定義 permission_callback,確保只有具備 manage_options 等相應權限的使用者才能觸發。這款外掛的 upload-logo 端點犯了兩個致命錯誤:第一,該路由完全缺乏有效的 permission_callback,任何 HTTP 請求無論有沒有合法 Session Cookie 或 Nonce 都能直接觸發後端邏輯;第二,處理常式並未使用 WordPress 內建的安全函式(如 wp_handle_upload 搭配 MIME 類型檢查),而是盲目接收上傳檔案,未檢查副檔名是否為合法圖片格式,也未對檔名做 Sanitization,導致 .php 檔案能直接落地在 Web 伺服器可解析執行的 wp-content/uploads/ 目錄,達成遠端程式碼執行(RCE)條件 ⛑️

🎯 攻擊鏈路:從探測到接管,只要三步

階段 技術動作
❶ 偵察 自動化機器人向 /wp-json/wp/v2/users 發送 GET 請求,收集現存管理員使用者名稱,為後續建立後門或混淆身分預作準備
❷ 惡意負載投遞 向漏洞端點(如 /wp-json/wplp/v1/upload-logo 或類似命名空間)發送特製 POST 請求,multipart/form-data 主體夾帶偽裝成 Logo 參數的惡意 PHP 腳本
❸ 落地與接管 伺服器將 .php 檔案寫入 wp-content/uploads/,攻擊者立即發送 GET 請求存取該檔案絕對 URL,Web 伺服器呼叫 PHP 解譯器執行惡意代碼,程式碼隨即在資料庫插入高權限新使用者,完成系統接管

🟡 合理推論(Probable Escalation):獨立安全研究團隊(如 WPScan)在後續驗證中發現,除了 upload-logo 之外,該外掛的 REST 命名空間中可能還存在其他兩條具備相同缺陷的寫入路徑【❓待確認:具體路由名稱原文未明確列出】。更嚴重的是,若程式碼未將路徑鎖死在 uploads 目錄,攻擊者可能透過路徑穿越(Path Traversal)把檔案寫入其他關鍵目錄,藉此繞過某些只針對 uploads 目錄設定的 WAF 規則 ⚠️

🔴 假設情境(Worst-Case):在防護薄弱的共享主機環境,若 Web 程序(如 www-data)權限沒有妥善限縮,攻擊者可能透過上傳的 Webshell 進一步執行底層作業系統指令,導致整台伺服器的所有服務與資料庫遭到勒索軟體加密或徹底摧毀 🚫


🔷 🔷 🔷

✅ 資安排查與自我檢查 Checklist

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

如果你的網站曾經跑過 WPLP Cookie Consent(外掛資料夾名稱/WP-CLI slug 為 gdpr-cookie-consent4.4.1 或更舊版本,真正棘手的問題就不再只是「現在有沒有漏洞」,而是「漏洞修掉以前,有沒有東西已經被寫進網站?」公開資安分析已明確指出,問題出在檔案上傳處理常式 saas_upload_logo() 缺乏檔案類型驗證,且疊加了 WPLP Connector REST 端點的授權繞過,兩者疊加才造成了這次的滅頂之災 😱

這也是為什麼這一節不採用「打一條掃描指令,看到綠色就收工」的做法,而是按照 環境判斷 👉 盤點 👉 Log 👉 後門檢查 👉 加固 的順序,把可能留下的痕跡一層層縮小,同時涵蓋單一網站、獨立多站、Multisite三種完全不同的維運情境 🧭

🕵️ 網站到底有沒有被摸過?先把現場一層一層翻出來

🔥 一句話先記住:「版本 ≥ 4.4.2」代表你已經跨過這個漏洞的修補門檻;它不等於「網站歷史上從來沒有被入侵」。如果舊版本曾暴露在網際網路上,版本修補與入侵排查應該視為兩件不同工作 🚷

🧭 ㊀ 先判斷環境:你到底是在管一個 WordPress,還是一整台 WordPress 伺服器?

這一步看似無聊,卻是後面所有批次指令能不能安全執行的地基。搞錯環境類型,輕則指令空跑,重則誤更新到不該動的網站 🧱

環境 你看到的結構 正確思路 常用 WP-CLI 定位方式
💠 單一網站 一個 WordPress 安裝 直接在該網站目錄操作 –path=”$WP_PATH”
❇️ 獨立多站 一台 VPS 上有多個完全獨立的 WordPress 每個 wp-config.php 都代表一個獨立安裝,各自的外掛檔案、資料庫都不共用 –path=”$path”
✴️ Multisite 一個 WordPress Network,底下有多個 Site 外掛檔案屬於整個 Network,不是每個 Site 各裝一份 –url=”$url”

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

決策原理:先確認目前路徑真的能載入 WordPress,再往下查外掛版本;這比直接執行一長串批次指令安全,也能避免對錯誤的目錄下手 🧩

※下方的網站路徑 /var/www/example.com/public_html 請替換為你的網站真實路徑,並保留雙引號以支援含空格的路徑:

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” 的雙引號,不要自行刪除,否則路徑會被 shell 拆成多個參數 🧯

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

決策原理:HestiaCP、cPanel 或自行管理 VPS 時,「同一台伺服器有很多 WordPress」不代表 Multisite。這時應該從各站的 wp-config.php 建立完整 Inventory,逐一驗證是否真的能被 WP-CLI 載入 📋

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 版本、檔案權限或路徑是否正確,不要直接更新。
  • 🟡 同一個網站被找到多個 wp-config.php(例如備份或 Staging 副本)👉 先人工確認哪個才是 Production,避免誤動到備份環境 🙅

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

決策原理:WP-CLI 官方文件將 wp site list 明確定義為列出 Multisite installation 中各個 Site 的指令,因此不能拿來掃描一台 VPS 上互不相干的獨立多站 📚

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 相關設定表(如 wp_blogs)才能下結論 🔎

🧑‍🔧 DevOps 小知識:「獨立多站」像同一棟大樓裡有三家公司,各自有自己的門、帳冊與倉庫;Multisite 則比較像同一家公司底下有三個部門,共用同一套外掛檔案與 WordPress Core。外觀看起來都是「三個網站」,但資料庫、外掛與更新邏輯完全不同,用錯思路批次操作,很容易誤傷不該動的站 🚫


🔎 🔎 🔎

🔎 ㊁ 盤點:先回答「哪些站裝了 WPLP、目前到底是哪一版?」

目前公開漏洞資料庫將 4.4.1 以下列入受影響範圍,4.4.2 為修補版本;WordPress.org 官方外掛頁面與威脅情資平台皆指向這個安全邊界 📊

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

決策原理:外掛版本盤點應以 WordPress 自己認得的 Plugin metadata 為主,而不是自己 grep PHP 檔案內容去猜版本;但如果主機環境沒有裝 WP-CLI,也要準備一條純檔案系統的備援指令 🛠️

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

wp --path="$WP_PATH" plugin get "gdpr-cookie-consent" \
    --fields=name,status,version,update,update_version

預期輸出:

name: gdpr-cookie-consent
status: active
version: 4.4.1
update: available
update_version: 4.4.2

如果主機沒有 WP-CLI,改用 readme.txt 交叉比對版本(僅供備援,優先度低於 WP-CLI):

PLUGIN_README="/var/www/example.com/public_html/wp-content/plugins/gdpr-cookie-consent/readme.txt"

if [ -f "$PLUGIN_README" ]; then
    grep -im1 "Stable tag" "$PLUGIN_README"
else
    echo "PLUGIN NOT FOUND: $PLUGIN_README"
fi

分支判斷:

  • 🔴 4.4.1 或更舊 👉 視為受影響,進入第 4 節的應急/修補流程。
  • 🟢 4.4.2 或更新 👉 此漏洞的版本條件已解除,但若網站曾使用舊版,仍要繼續做入侵排查,不能因為版本新就跳過 ⭕
  • 🟠 找不到外掛 👉 確認是否已被移除、是否改用其他 Slug,並確認沒有查錯網站路徑。

❇️ 獨立多站:逐站查,不要把不同網站混成一份結果

決策原理:每個獨立 WordPress 都有自己的外掛安裝狀態,因此必須逐一使用 –path,並把結果整理成一張「風險地圖」🗺️

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 "gdpr-cookie-consent" \
        --field=version 2>/dev/null || true)"

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

預期輸出:

Site: /var/www/site-a/public_html | WPLP: 4.4.2
Site: /var/www/site-b/public_html | WPLP: 4.4.1
Site: /var/www/site-c/public_html | WPLP: 4.3.9

分支判斷:這份結果就是你的第一張風險地圖:B、C 站應列入緊急修補名單;A 站可以先降級為一般歷史排查優先度 📌

✴️ Multisite:外掛檔案只盤點一次,再從 Site 層確認啟用狀態

決策原理:Multisite 共用同一套外掛檔案,因此不能把每個 Site 當成一個獨立外掛安裝來重複盤點 🧩

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

wp --path="$WP_PATH" plugin get "gdpr-cookie-consent" \
    --fields=name,status,version,update,update_version

wp --path="$WP_PATH" site list \
    --field=url

預期輸出:

name: gdpr-cookie-consent
status: active
version: 4.4.1
update: available
update_version: 4.4.2

https://example.com/
https://shop.example.com/
https://blog.example.com/

分支判斷:外掛版本以 Network 安裝的檔案為核心;Site 層則用 –url=”$url” 逐一確認各站載入後的實際啟用狀態,WP-CLI 官方也將 –url 定義為 Multisite 指定目標 Site 的方式 📖


🧾 🧾 🧾

🧾 ㊂ Log 排查:不是看到請求就緊張,而是找「路徑+方法+結果」的組合

公開漏洞描述指出問題核心是 saas_upload_logo() 函式缺乏檔案類型驗證,並疊加了 WPLP Connector REST 端點的授權繞過。因此排查 Log 時,真正有價值的不是找某個「神奇 IP」,而是確認是否曾出現對應 REST 路徑、HTTP 方法與異常回應碼的組合 🧐

💡 碎碎念:官方公開資訊目前只確認端點命名與 upload-logosaas_upload_logo() 相關,但完整 REST 命名空間路徑並未逐一公開列出【❓待確認:完整路由清單】,因此以下 grep 採「多關鍵字並列」策略,避免因為猜錯確切路徑而漏掉真正的攻擊紀錄 🌠

💠 單一網站:先找得到 Log,再做關鍵字命中搜尋

決策原理:不同主機環境的 Access Log 路徑完全不同,因此不要把某一種路徑當成所有伺服器的固定答案,先確認 Log 存在,再往下搜尋 📜

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

if [ -f "$LOG_FILE" ]; then
    grep -Ei \
        'upload-logo|saas_upload_logo|wplp|gdpr-cookie-consent.*upload' \
        "$LOG_FILE" |
    tail -n 100
else
    echo "LOG NOT FOUND: $LOG_FILE"
fi

預期輸出:

LOG NOT FOUND: /var/log/apache2/access.log

這不是失敗,而是提醒你:先找出實際 VirtualHost/Web Server 使用的 Log 路徑(Nginx 常見於 /var/log/nginx/,部分主機面板如 HestiaCP 則會放在使用者專屬目錄下)。如果找到 Log,可能看到類似:

... "GET /wp-json/wp/v2/users HTTP/1.1" 200 ...
... "POST /wp-json/wplp-connector/v1/upload-logo HTTP/1.1" 403 ...
... "POST /wp-json/wplp-connector/v1/upload-logo HTTP/1.1" 200 ...

分支判斷:

  • 🟢 只有 403/拒絕紀錄 👉 代表請求曾抵達,但不能單憑這一點判定成功入侵。
  • 🟡 出現 2xx 回應 👉 提高事件優先級,接著比對同一時間點的檔案建立/修改紀錄。
  • 🔴 同一時間附近出現可疑新檔案、異常管理員帳號或其他異常 👉 立刻進入第 4 節「A. 已中招/疑似中招」流程 🚨

❇️ 獨立多站:每一個 VirtualHost 都要對應自己的 Log

決策原理:不要只查其中一站,漏洞如果存在於多個獨立 WordPress,任何一站都有可能成為受影響對象 🔍

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 \
        'upload-logo|saas_upload_logo|wplp|gdpr-cookie-consent.*upload' \
        "$log" 2>/dev/null || true)"

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

預期輸出:只會列出有命中的 Log 檔案,方便你把注意力集中在真的需要調查的站點。

分支判斷:某站出現命中 👉 把該站標記為需要進一步鑑識;全部沒有命中 👉 風險降低,但仍不能證明「絕對沒有被攻擊」,因為 Log 可能已被輪替、清空或位於其他服務(如 CDN 邊緣節點)🧯

✴️ Multisite:同一份 Web Server Log,從 URL/時間再定位 Site

決策原理:Multisite 通常共用一個 WordPress installation,甚至共用同一個 VirtualHost,因此 Log 排查要把 HTTP Host、URL 與 Site 清單對照,才能定位是哪個 Site 受害 🎯

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

if [ -f "$LOG_FILE" ]; then
    grep -Ei \
        'upload-logo|saas_upload_logo|wplp|gdpr-cookie-consent.*upload' \
        "$LOG_FILE" |
    tail -n 100
fi

分支判斷:找到可疑時間點後,再使用 wp site list 取得 Network Site 清單,依 Log 中的 Host/URL 判斷哪個 Site 受到影響。這一步不要只看「外掛是不是啟用」,因為 Network 中各 Site 的實際使用狀態可能不同 📔

🚨 畫重點!千萬別踩雷:Log 裡出現 upload-logo 並不等於「一定成功入侵」;同樣地,完全沒有找到這個字串,也不能保證「完全沒被攻擊」。Log 是證據之一,不是唯一證據 🙅


🗂️ 🗂️ 🗂️

🗂️ ㊃ 後門檢查點:檔案、帳號、排程一次盤到底

這個漏洞值得優先檢查檔案的原因很直觀:問題本身就是任意檔案上傳。因此,如果舊版本曾暴露在網路上,wp-content/uploads/ 是最值得優先看的位置。但駭客留下的未必只有檔案,管理員帳號、資料庫設定與排程也要一起檢查,才能算是完整的威脅狩獵 🕵️‍♀️

💠 單一網站:uploads 可執行檔、管理員帳號、排程與快照一次到位

決策原理:正常 WordPress 媒體庫不應以「讓 PHP 在 uploads 裡執行」作為一般工作流程;同時,惡意程式碼執行後常會直接修改 wp_users 資料表,無中生有建立一個 administrator 權限的幽靈帳號,還可能把建立時間「回推」到過去,藉此躲避日常巡查,因此要一併排查 🔦

WP_PATH="/var/www/example.com/public_html"
UPLOADS="$WP_PATH/wp-content/uploads"
EVIDENCE_DIR="$HOME/wplp-evidence-$(date +%Y%m%d_%H%M%S)"

mkdir -p "$EVIDENCE_DIR"

{
    echo "=== 1. 可疑可執行檔(uploads 目錄不該出現的副檔名) ==="
    if [ -d "$UPLOADS" ]; then
        find "$UPLOADS" -type f \
            \( -iname "*.php" -o -iname "*.phtml" -o -iname "*.php*" \
               -o -iname "*.phar" \) \
            -printf '%TY-%Tm-%Td %TH:%TM:%TS %u:%g %m %p\n'
    else
        echo "UPLOADS DIRECTORY NOT FOUND: $UPLOADS"
    fi

    echo ""
    echo "=== 2. 管理員帳號清單 ==="
    wp --path="$WP_PATH" user list \
        --role=administrator \
        --fields=ID,user_login,user_email,display_name,user_registered \
        --format=table

    echo ""
    echo "=== 3. 近 10 天內異動的核心檔案 ==="
    find "$WP_PATH" -maxdepth 1 -type f -mtime -10 -print

    echo ""
    echo "=== 4. 目前啟用的外掛清單(比對是否有偽裝外掛) ==="
    wp --path="$WP_PATH" plugin list \
        --fields=name,status,version \
        --format=table

    echo ""
    echo "=== 5. 排程事件(Cron) ==="
    wp --path="$WP_PATH" cron event list \
        --fields=hook,next_run_gmt,recurrence \
        --format=table
} > "$EVIDENCE_DIR/backdoor-check.txt" 2>&1

echo "Evidence directory: $EVIDENCE_DIR"

預期輸出(節錄):

=== 1. 可疑可執行檔(uploads 目錄不該出現的副檔名) ===
2026-08-26 14:03:11 www-data:www-data 644 /var/www/example.com/public_html/wp-content/uploads/2026/08/logo-final.php

=== 2. 管理員帳號清單 ===
ID  user_login  user_email          display_name       user_registered
1   admin       admin@example.com    Site Admin         2026-01-10 08:30:00
9   supportbot  support@unknown.io   IT Support          2026-08-26 14:03:11

Evidence directory: /home/admin/wplp-evidence-20260904_120000

分支判斷:

  • 🟢 找不到可疑檔案、帳號皆為熟面孔 👉 這一輪沒有發現異常,可繼續往加固檢查前進。
  • 🔴 出現不屬於部署流程的可執行檔,或帳號「建立時間精確到秒都與可疑上傳時間吻合」👉 不要直接刪除,先保留這份 $EVIDENCE_DIR 證據快照,再進入第 4 節「A. 已中招/疑似中招」流程。
  • 🟡 找到你自己合法建立的 PHP 檔案或近期確實有異動核心檔案 👉 記錄原因(例如主機商維護、自行部署),不要一律當成惡意檔案處理 📔

❇️ 獨立多站:每站各自檢查,結果一律帶著網站路徑

決策原理:每個網站獨立判斷,不能因為 A 站乾淨,就假設 B、C 站也一起沒事;證據目錄也要各站分開存放,避免資料混淆 📦

SEARCH_ROOT="/var/www"
EVIDENCE_ROOT="$HOME/wplp-evidence-$(date +%Y%m%d_%H%M%S)"

mkdir -p "$EVIDENCE_ROOT"

find "$SEARCH_ROOT" -type f -name "wp-config.php" -print0 |
while IFS= read -r -d '' config; do
    path="$(dirname -- "$config")"
    safe_name="$(printf '%s' "$path" | tr '/ ' '__')"
    evidence="$EVIDENCE_ROOT/$safe_name"
    uploads="$path/wp-content/uploads"

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

    mkdir -p "$evidence"

    {
        echo "=== 可疑可執行檔:$path ==="
        find "$uploads" -type f \
            \( -iname "*.php" -o -iname "*.phtml" -o -iname "*.php*" \) \
            -printf '%TY-%Tm-%Td %TH:%TM:%TS %u:%g %m %p\n' \
            2>/dev/null

        echo ""
        echo "=== 管理員帳號:$path ==="
        wp --path="$path" user list \
            --role=administrator \
            --fields=ID,user_login,user_email,display_name,user_registered \
            --format=table

        echo ""
        echo "=== 排程事件:$path ==="
        wp --path="$path" cron event list \
            --fields=hook,next_run_gmt,recurrence \
            --format=table
    } > "$evidence/backdoor-check.txt" 2>&1
done

echo "Evidence root: $EVIDENCE_ROOT"

分支判斷:某站出現未知管理員或可疑檔案 👉 優先標記該站進入事件調查,不要因為同一台 VPS 其他站看起來正常就跳過它;每個子目錄就是一個獨立站點的鑑識資料,後續才能把「哪個網站、哪個時間、哪個檔案」對起來 🧩

✴️ Multisite:Uploads 與帳號都要回到 Network 層級思考

決策原理:Multisite 的權限模型與單站不同,Uploads 目錄通常也位於同一個 Network 安裝之下,因此不要直接把單站的判斷邏輯原封不動套過來 🧠

WP_PATH="/var/www/network/public_html"
UPLOADS="$WP_PATH/wp-content/uploads"
EVIDENCE_DIR="$HOME/wplp-multisite-evidence-$(date +%Y%m%d_%H%M%S)"

mkdir -p "$EVIDENCE_DIR"

{
    echo "=== 可疑可執行檔(Network Uploads) ==="
    if [ -d "$UPLOADS" ]; then
        find "$UPLOADS" -type f \
            \( -iname "*.php" -o -iname "*.phtml" -o -iname "*.php*" \) \
            -printf '%TY-%Tm-%Td %TH:%TM:%TS %u:%g %m %p\n'
    fi

    echo ""
    echo "=== 全站使用者清單(含各 Site 角色) ==="
    wp --path="$WP_PATH" user list \
        --fields=ID,user_login,user_email,display_name,user_registered \
        --format=table

    echo ""
    echo "=== Network Site 清單 ==="
    wp --path="$WP_PATH" site list \
        --fields=blog_id,url \
        --format=table

    echo ""
    echo "=== Network 層排程事件 ==="
    wp --path="$WP_PATH" cron event list \
        --fields=hook,next_run_gmt,recurrence \
        --format=table
} > "$EVIDENCE_DIR/backdoor-check.txt" 2>&1

echo "Evidence directory: $EVIDENCE_DIR"

分支判斷:找到異常檔案或帳號後,再依檔案路徑與時間對照 Network Site、Access Log 與部署紀錄,確認實際受影響的是哪個 Site,不要因為它位於 Network Uploads 就直接指定某一站為受害者 🔍


🧱 🧱 🧱

🧱 ㊄ 加固檢查:就算修好了,也把第二道門鎖起來

這起漏洞的根本教訓之一,就是「檔案上傳」不能只依賴應用程式本身的驗證邏輯。公開防禦建議也指出,如果暫時無法更新,可以從 Web Server/WAF 層限制相關 REST 連接埠,並讓 Uploads 目錄徹底無法執行 PHP,這是防禦 CWE-434 類漏洞最核心的最後一道防線 🛡️

💠 單一網站:先檢查 uploads 是否已有 PHP 執行防護

決策原理:防禦目標是「即使某個元件錯誤地把危險檔案寫進 uploads,也不要讓 Web Server 把它當成 PHP 程式執行」,這是低成本、高效益的縱深防禦 🔒

UPLOADS="/var/www/example.com/public_html/wp-content/uploads"

if [ -d "$UPLOADS" ]; then
    find "$UPLOADS" -maxdepth 2 -type f \
        -name ".htaccess" \
        -print
    printf '\n目前 Apache 是否已封鎖 uploads 執行 PHP,請人工檢查上列檔案內容\n'
else
    echo "UPLOADS DIRECTORY NOT FOUND: $UPLOADS"
fi

預期輸出:可能沒有輸出,也可能看到既有 WordPress/主機管理系統(如 HestiaCP)產生的設定檔。

分支判斷:這一步只做檢查,不直接覆寫既有 Web Server 設定;因為 HestiaCP、Apache、Nginx、PHP-FPM 的實際架構不同,直接貼一段設定到 Production 反而可能造成新的問題,應先在 Staging 驗證後再套用 🧪

❇️ 獨立多站:逐站確認防護是否一致

決策原理:獨立多站最常見的加固漏洞,就是「某一站設定過但其他站忘記做」,因此要逐站檢查是否一致 📋

SEARCH_ROOT="/var/www"

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

    if [ ! -d "$uploads" ]; then
        continue
    fi

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

    if find "$uploads" -maxdepth 1 -type f -name ".htaccess" -print -quit | grep -q .; then
        echo "已存在 .htaccess,請人工確認內容是否已封鎖 PHP 執行"
    else
        echo "未發現 .htaccess,建議列入加固待辦清單"
    fi
done

分支判斷:每一站獨立記錄結果,統一整理成「加固待辦清單」;不要因為其中一站已加固,就假設其他站也一併完成 ✅

✴️ Multisite:Uploads 通常共用同一份,加固一次即可覆蓋全部 Site

決策原理:Multisite 的 Uploads 目錄結構通常位於同一個 Network 安裝之下(各 Site 再依 sites/{blog_id}/ 分目錄),因此在 Network 層級做好防護,理論上能覆蓋所有 Site 的媒體上傳路徑 🌳

WP_PATH="/var/www/network/public_html"
UPLOADS="$WP_PATH/wp-content/uploads"

if [ -d "$UPLOADS" ]; then
    find "$UPLOADS" -maxdepth 3 -type f \
        -name ".htaccess" \
        -print
fi

分支判斷:Network 層加固完成後,仍建議抽樣挑幾個子站點的 sites/{blog_id}/ 子目錄確認是否也一併受到保護,避免因為主題/外掛自訂上傳路徑而漏掉防護範圍 🧯

💡 延伸查證:本節提到的 saas_upload_logo() 函式名稱與「WPLP Connector REST 端點授權繞過」描述,來自官方漏洞資料庫對此 CVE 的技術描述;外掛官方 GitHub 版本庫(wpeka/gdpr-cookie-consent)與 WordPress.org 外掛頁面的更新紀錄則以「強化程式碼安全與驗證邏輯、改善資料處理與匯出可靠性」等概括性文字描述本次修補,並未逐行公開修補前後的完整原始碼 diff,因此本文只反推「應該加固哪些防禦點」,不還原任何攻擊細節 🔑


🛟 🛟 🛟

🛟 真的出事了怎麼救?從「先止血」一路走到「確認關門」

前一節解決的是「怎麼判斷」,這一節則進入真正的維運 Runbook。無論你屬於哪一種情境,貫穿全程的判斷鏈都是同一條:判斷環境 👉 盤點 👉 備份 👉 Dry-run 👉 更新 👉 驗證,每個階段都要走完,不能因為前面順利就跳過後面的驗證步驟 🧭

🔥 這裡最重要的閉環:不是「WP-CLI 顯示 Updated successfully」就代表結束,而是版本已跨過 4.4.2、備份可用、Checksum 正常、沒有新的可疑檔案/帳號/Log,前台與後台功能也正常,才算真正收工 🦸

🚨 A. 已中招/疑似中招情境:先止血、留證,再談修補

如果你在第 3 節已經找到可疑 PHP 檔案、陌生管理員、異常 REST Log 或其他明確入侵跡象,處理順序就跟「單純漏洞更新」不一樣了——第一反應絕對不是 rm -rf 或急著清除帳號,而是先建立事件狀態、保留證據 🧯

💠 單一網站:先建立事件狀態,再決定是否隔離

決策原理:疑似入侵時,最大的敵人不是漏洞本身,而是在沒有留下證據的情況下直接清理,導致後續無法追溯攻擊時間軸與範圍 ❤️‍🩹

WP_PATH="/var/www/example.com/public_html"
INCIDENT_DIR="$HOME/wplp-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 "gdpr-cookie-consent" \
        --fields=name,status,version,update,update_version

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

echo "Incident evidence: $INCIDENT_DIR"

預期輸出:

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

分支判斷:證據已建立 👉 再進行止血動作(見下方 B 情境的緩衝措施);如果是企業、電商或會員制網站,應同步通知內部資安或維運負責人,並依內部資安事件應變流程留存證據以利後續稽核 ⚠️

❇️ 獨立多站:只隔離有證據的站,避免整台 VPS 一起誤傷

決策原理:獨立多站的最大優勢就是隔離性。某一站疑似遭入侵時,先確認受影響網站範圍,再依主機架構進行站點級隔離,不要因為緊張就把整台伺服器的所有站一起停掉 🛑

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

分支判斷:確認路徑正確 👉 再對該站執行後續維護;不要把整個 /var/www 當成單一網站處理,也不要因為 B 站中招就連帶停用 A、C 站的正常服務 🙅

✴️ Multisite:先把事件視為 Network 級問題

決策原理:Multisite 共用核心、外掛與 Network 資源,因此某個 Site 出現入侵跡象時,不能只針對那個 Site 做處理就宣稱「完成」,必須把整個 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 "gdpr-cookie-consent" \
    --fields=name,status,version,update,update_version

分支判斷:任一 Site 出現明確入侵跡象 👉 把整個 Network(含所有 Site)納入事件調查範圍;即使其他 Site 目前看起來正常,也要一併檢查是否共用了同一批帳號或設定 🔍


🧯 🧯 🧯

🧯 B. 短期應急:尚未確認中招,但現在還不能立刻更新

短期應急的目標不是「修好漏洞」,而是縮短攻擊面暴露時間,作為正式修補前的緩衝措施 🦺

🚨 短期應急 ≠ 正式修補。WAF/Web Server 限制、停用外掛或禁止 uploads 執行 PHP 都屬於緩衝措施;真正解決漏洞的仍然是升級到 4.4.2 以上,不能把緩衝措施當成長期解方 🔓

💠 單一網站:能停用時優先降低暴露面,Web Server 層同步補強

決策原理:如果網站目前不急需 WPLP Cookie Consent 功能,而正式更新必須等維護窗口,停用受影響外掛通常比讓它持續暴露在公網更容易控制;若業務依賴此功能無法停用,則優先在 Web Server 層封鎖漏洞端點 🛟

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

wp --path="$WP_PATH" plugin deactivate "gdpr-cookie-consent"

預期輸出:

Plugin 'gdpr-cookie-consent' deactivated.
Success: Deactivated 1 of 1 plugins.

若無法停用,改在 Nginx 站點設定區塊封鎖漏洞相關端點(僅為緩衝,正式修補後應重新評估是否仍需保留此規則):

location ~* "^/wp-json/.*(upload-logo|wplp).*$" {
    if ($request_method = POST) {
        return 403;
    }
}

location ~* "/wp-content/uploads/.*\.php$" {
    deny all;
}

Apache 環境則在 wp-content/uploads/ 根目錄下建立或修改 .htaccess

<FilesMatch "\.(php|phtml|php5|phar)$">
    Require all denied
</FilesMatch>

分支判斷:

  • 🟢 可以安全停用外掛 👉 保留停用狀態,盡快排入正式修補的維護窗口。
  • 🟠 網站核心功能依賴它(例如合規橫幅是法遵必要項目)👉 不要在 Production 直接停用,改採 Web Server/WAF 層緩衝規則,並儘速安排升級。

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

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

wp --path="$INCIDENT_PATH" plugin deactivate "gdpr-cookie-consent"

分支判斷:只影響 Site B,不應連 Site A、Site C 一起停掉;每一站的應急狀態都要各自記錄在案,方便日後排程正式修補 📝

✴️ Multisite:先確認外掛是否為 Network Activated

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

if wp --path="$WP_PATH" plugin is-active \
    "gdpr-cookie-consent" \
    --network; then
    echo "WPLP is Network Activated."
else
    echo "WPLP is not Network Activated."
fi

預期輸出:

WPLP is Network Activated.

分支判斷:如果是 Network Activated,處理時要把整個 Network 視為影響範圍;不要以為只關閉某個 Site 的個別設定,就能把共用的外掛檔案隔離掉 🧩

🧑‍🔧 DevOps 小知識:WP-CLI 的 –network 是針對 Network 層的操作旗標;Multisite 的 –url=”$url” 則是指定某一個特定 Site。兩者不是同一個概念,混用容易造成操作範圍誤判 🙅


🚀 🚀 🚀

🚀 C. 正式修補:判斷鏈完整跑一次(備份 👉 Dry-run 👉 更新 👉 驗證)

目前公開資料明確將 4.4.1 以下列為受影響範圍,4.4.2 為安全版本,WordPress.org 官方外掛頁面也已列出對應更新紀錄 ✅

⚠️ 安全提醒:在正式環境(Production)升級外掛前,務必先於測試環境(Staging)備份並驗證,避免網站崩潰或版面跑版 🛟

💠 單一網站:一次走完完整判斷鏈

① 備份 —— 決策原理:外掛更新主要改的是外掛檔案,但網站內容、設定與媒體檔案仍可能需要恢復,因此至少要有資料庫備份,並依既有維運政策保留必要檔案 💾

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_wplp_update_${STAMP}.sql"

tar -czf \
    "$BACKUP_DIR/example-com_wp-content_pre_wplp_update_${STAMP}.tar.gz" \
    -C "$WP_PATH" \
    "wp-content"

printf 'Backup completed:\n%s\n' "$BACKUP_DIR"

預期輸出:

Success: Exported to '/home/admin/wp-security-backup/example-com_pre_wplp_update_20260904_123000.sql'.
Backup completed:
/home/admin/wp-security-backup

分支判斷:SQL 與檔案備份都存在 👉 進入 Dry-run;任一備份失敗 👉 停止,不進入正式更新 🚫

② Dry-run —— 決策原理:WP-CLI 官方的 wp plugin update 支援 –dry-run,用途就是預覽哪些外掛會被更新,而不真正執行更新,適合在正式更新前先確認目標版本 🧪

wp --path="$WP_PATH" plugin update \
    "gdpr-cookie-consent" \
    --dry-run

預期輸出:

Plugin gdpr-cookie-consent 4.4.1 will be updated to 4.4.2.

分支判斷:看到目標版本 👉 可以正式更新;沒有可用更新 👉 回頭確認目前版本與外掛來源,不要因為「No updates」就直接認定網站安全 🧐

③ 更新 —— 決策原理:更新一次、驗證一次,不要連續執行多次更新指令,以免因網路或資料庫鎖定造成不一致狀態 🔄

wp --path="$WP_PATH" plugin update \
    "gdpr-cookie-consent"

預期輸出:

Updating 'gdpr-cookie-consent'...
Plugin updated successfully.
Success: Updated 1 of 1 plugins.

分支判斷:更新成功 👉 立即進版本與 Checksum 驗證;更新失敗 👉 不要反覆執行,先查看錯誤訊息、確認檔案權限與磁碟空間,必要時從備份還原後重試 🔧

④ 驗證 —— 決策原理:驗證要同時涵蓋「版本是否真的更新」「檔案是否完整未被竄改」「功能是否正常」三個層面,缺一不可 🔍

wp --path="$WP_PATH" plugin get \
    "gdpr-cookie-consent" \
    --fields=name,status,version,update,update_version

wp --path="$WP_PATH" plugin verify-checksums \
    "gdpr-cookie-consent"

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

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

預期輸出:

name: gdpr-cookie-consent
status: active
version: 4.4.2
update:
update_version:

Success: Verified 1 of 1 plugins.
Success: WordPress installation verifies against checksums.

分支判斷:

  • 🟢 版本 4.4.2+Checksum 皆 Verified 👉 再實際打開前台確認 Cookie 橫幅正常彈出,後台可正常登入,即完成修補。
  • 🔴 Checksum mismatch 👉 不要忽略,代表官方檔案 checksum 比對不符,需檢查是否有人手動修改過外掛檔案,並進一步做入侵鑑識。
  • 🟡 Checksum 無法驗證 👉 不代表一定遭入侵,可能是 WordPress.org 沒有對應 checksum 或版本來源不同,需人工確認來源合法性。

🚨 Checksum 不是「無罪證明書」。它主要回答:「這些可由 WordPress.org 提供 checksum 的外掛檔案,現在是否符合官方檔案?」它無法回答 uploads、mu-plugins、其他外掛、佈景主題、資料庫、管理員帳號與 Web Server 是否曾遭入侵,這些仍要依賴第 3 節的排查結果一起判斷 🦹‍♂️

❇️ 獨立多站:一站一個完整閉環,逐站完成才進下一站

決策原理:三個獨立 WordPress 就是三個不同的資料庫 context,因此備份、Dry-run、更新、驗證都必須各自完整跑完,不能「全部更新完最後才一起檢查」🧩

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

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

    if [ -z "$version" ]; then
        continue
    fi

    site_name="$(basename "$path")"
    db_file="$BACKUP_ROOT/${site_name}_pre_wplp_update_${STAMP}.sql"

    printf '\n===== %s | 目前版本=%s =====\n' "$path" "$version"

    echo "-- ① 備份 --"
    wp --path="$path" db export "$db_file"

    echo "-- ② Dry-run --"
    wp --path="$path" plugin update \
        "gdpr-cookie-consent" \
        --dry-run

    echo "-- ③ 更新 --"
    wp --path="$path" plugin update \
        "gdpr-cookie-consent"

    echo "-- ④ 驗證 --"
    wp --path="$path" plugin get \
        "gdpr-cookie-consent" \
        --fields=name,status,version

    wp --path="$path" plugin verify-checksums \
        "gdpr-cookie-consent"
done

預期結果:每個站點區塊都應各自顯示「Backup → Dry-run 目標版本 → Updated successfully → 4.4.2 → Verified」的完整流程,缺任何一段都代表該站未完成閉環。

分支判斷:某一站在任何一步失敗(備份、Dry-run、更新或驗證任一環節)👉 立刻停止該站後續步驟並標記為未完成,不要因為其他站成功就直接略過;建議第一批 Production 仍採「一站完成 👉 驗證 👉 下一站」的節奏,而非一次批次跑完所有站 🚦

✴️ Multisite:外掛檔案只走一次完整流程,再逐 Site 驗證

決策原理:Multisite Site 並不是 N 個獨立 WordPress 安裝,對 Network 執行一次 wp db export、一次 wp plugin update,就能覆蓋所有共用此外掛的 Site,不需要對每個 Site URL 重複整套流程 🌳

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"

echo "-- ① 備份(Network Database+必要檔案) --"
wp --path="$WP_PATH" db export \
    "$BACKUP_DIR/multisite_pre_wplp_update_${STAMP}.sql"

tar -czf \
    "$BACKUP_DIR/multisite_wp-content_pre_wplp_update_${STAMP}.tar.gz" \
    -C "$WP_PATH" \
    "wp-content"

echo "-- ② Dry-run --"
wp --path="$WP_PATH" plugin update \
    "gdpr-cookie-consent" \
    --dry-run

echo "-- ③ 更新 --"
wp --path="$WP_PATH" plugin update \
    "gdpr-cookie-consent"

echo "-- ④ Network 層版本+Checksum 驗證 --"
wp --path="$WP_PATH" plugin get \
    "gdpr-cookie-consent" \
    --fields=name,status,version

wp --path="$WP_PATH" plugin verify-checksums \
    "gdpr-cookie-consent"

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

echo "-- ⑤ 逐 Site 功能驗證 --"
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

    wp --path="$WP_PATH" \
        --url="$url" \
        plugin get "gdpr-cookie-consent" \
        --fields=name,status,version
done

💡 這裡可以把 Multisite 想成「一間公司、20 個部門」:20 個部門不代表公司有 20 本完全獨立的總帳。備份、Dry-run、更新都只需要在 Network 層做一次,但驗證階段仍要逐一確認每個 Site 是否正常載入與啟用,因為各 Site 的實際使用狀態可能不同 🕵️‍♀️

分支判斷:Network 層備份/Dry-run/更新/Checksum 全數成功,且逐 Site 驗證皆正常載入 👉 完成修補;任一 Site 出現錯誤 👉 暫停批次流程,先處理該 Site,不要繼續擴大變更範圍到其他 Site 🛑


🔍 🔍 🔍

🔍 修補後最後複查:確認沒有留下新的異常

不論是哪一種環境,修補完成後都建議做最後一輪 Log 與檔案複查,確認更新過程本身沒有引入新的問題,也沒有殘留修補前的異常紀錄 🧾

WP_PATH="/var/www/example.com/public_html"
LOG_FILE="/var/log/apache2/access.log"
UPLOADS="$WP_PATH/wp-content/uploads"

if [ -f "$LOG_FILE" ]; then
    grep -Ei \
        'upload-logo|saas_upload_logo|wplp' \
        "$LOG_FILE" |
    tail -n 50
fi

find "$UPLOADS" -type f \
    \( -iname "*.php" -o -iname "*.phtml" -o -iname "*.php*" \) \
    -print

分支判斷:更新後仍持續出現可疑 REST 請求或新的可疑檔案 👉 不要把它當成「已修好所以沒事」,應立刻回到第 4 節「A. 已中招/疑似中招」的事件調查流程,而不是單純重新更新外掛了事 🚨


🧾 🧾 🧾

🧾 最終驗收:這幾格全部打勾,才算真正收工

驗收項目 💠 單一網站 ❇️ 獨立多站 ✴️ Multisite
① 環境判斷 WP Path 逐站 wp-config.php Network + Site List
② 版本 4.4.2+ 每站 4.4.2+ Network 外掛 4.4.2+
③ 備份 DB+必要檔案 每站獨立 DB Network DB+必要檔案
④ Dry-run 已完成 逐站完成 Network 完成
⑤ Checksum 外掛+Core Verified 逐站 Verified Network Verified
⑥ Log/檔案複查 無新異常 逐站無新異常 Network/重要 Site 無新異常
⑦ 功能驗證 前台+後台正常 逐站驗證 重要 Site+Network 正常

完成標準:不是「WP-CLI 告訴你 Updated successfully」就算結束,而是版本已跨過 4.4.2、備份可用、Checksum 正常、沒有新的可疑檔案/帳號/Log,前台與後台也正常運作,才算真正把這扇門鎖回去 🦸

🫂 新手求助:完全看不懂上面這些指令怎麼辦?如果你不熟悉 SSH 或伺服器操作,最快的方式是直接把這份技術檢查清單轉交給你的網站維護工程師、外包商或主機代管服務商,請他們優先執行這項重大安全更新,並協助檢查是否有遭到入侵的跡象。多數台灣主機商都提供技術支援管道,遇到資安緊急事件不用不好意思開口求助,這比自己硬著頭皮亂改設定安全得多。若需要查證 WP-CLI 各項指令的官方定義,可參考 developer.wordpress.org/cli 的指令參考手冊 ⛑️

🔥 最後一句話:這起漏洞真正值得留下來的 DevOps 經驗,不是某一條神奇指令,而是把「漏洞修補」做成一個可以重複執行、可以驗證、可以回復的維運流程:判斷環境 👉 盤點 👉 備份 👉 Dry-run 👉 更新 👉 驗證。這樣即使下一次換成別的外掛、別的 CVE,整套方法依然能繼續派上用場 💪


📎 📎 📎

📚 參考資料與延伸資源

列印本文 [58] 👨‍👩‍👧‍👦 33 次瀏覽