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

你家表單外掛的收發員,正在幫駭客開後門?Super Forms 高危漏洞全解析

內容目錄

🚨 你按下「立即更新」之前,駭客可能早就把炸彈丟進你家儲藏室了:Super Forms 漏洞全解析

🔥 懶人包:
廣受歡迎的 WordPress 拖曳式表單外掛 Super Forms – Drag & Drop Form Builder,被抓到一個「完全不用登入、不用任何帳號密碼,就能直接把後門程式塞進你伺服器」的高危漏洞(CVE-2026-14894,CVSS 9.8 嚴重)。可怕的地方不是攻擊手法多高明,而是它把「身分驗證」跟「檔案檢查」這兩道最基本的門同時拆掉了。目前已累計偵測超過 25 萬次實際攻擊,強烈建議所有還在用 6.3.313 以下版本的網站,立刻升級到 6.3.314 以上 ☄️

🧊 冷知識:admin-ajax.php 其實是個活了快 20 年的老骨董
在 WordPress 把 REST API 收進核心(4.7 版)之前,/wp-admin/admin-ajax.php 是官方唯一推薦處理前端非同步請求的窗口,很多老牌外掛到現在都還深度依賴它。矛盾的地方在於,這個檔案每被呼叫一次,伺服器都得跑完整套 WordPress 啟動流程,效能不算好;更麻煩的是,只要外掛在對應的掛勾上漏寫一行權限驗證,整個網站的側門就對全世界敞開了,這次的 Super Forms 剛好就是活生生的案例 💡

🌐 前往官網確認版本 [53]

[54]


🟦 🟦 🟦

🧐 為什麼一個「表單外掛」也能變成駭客的引爆器?

先問你一個問題:如果俱樂部的櫃檯人員,不只不檢查訪客身分,還會主動印一張假的通行證塞給任何路過的陌生人——你覺得這間俱樂部還算安全嗎?這次 Super Forms 出包的邏輯,就是這麼直白 🛹

💡 白話文教室:把你的網站想成一間有門禁的私人俱樂部
你的 WordPress 網站就像一間有點格調的私人俱樂部,訪客要遞交東西,都得先經過大廳櫃檯,這個角色就是 Super Forms 在扮演的——訪客填表單、上傳附件,都是先經過它,才送進俱樂部後方的儲藏室(也就是你的伺服器目錄)。正常流程裡,櫃檯該做兩件事:確認來人有資格遞件(身分驗證),以及檢查包裹裡裝的是不是安全的東西(檔案類型驗證)。而這次爆出來的問題,剛好是這兩件事同時失守——櫃檯不但沒攔人,甚至還會主動發一張「臨時通行證」給任何伸手要的路人 🏢

身分驗證這一關會破,是因為 WordPress 系統裡的 Nonce(臨時通行證)機制被繞過了。Super Forms 表單提交端點 super_submit_form 是一支 nopriv 的 AJAX handler,意思是不用登入也能觸發;但唯一的防線只有一道 Session Nonce 檢查,而外掛裡另一個端點 super_create_nonce 同樣是 nopriv,會大方地發合法通行證給任何路人。這道防線等於是自己把鑰匙留在門口 🗝️

💡 碎碎念:Nonce 的全名很拗口,設計初衷卻很單純
Nonce 全名是「Number used ONCE」,聽起來像拋棄式一次性密碼,但實際上一張通行證的有效期長達 12 到 24 小時,並不是字面上「用一次就作廢」那麼乾脆。它原本的任務是防止惡意網站冒用你的身分偷偷發送請求(CSRF 攻擊),這次會出包,就是因為駭客找到另一條路,自己就能合法印出這張通行證,第一道防線瞬間破功 🌠

檔案檢查這一關破得更徹底。駭客把惡意 PHP 程式碼轉成 Base64 編碼,包裝成看起來像圖片格式的資料,塞進表單上傳欄位遞出去。而 Super Forms 的處理邏輯完全沒有檢查這包東西「實際上」是什麼格式,就直接把它原封不動寫進伺服器資料夾,還照著駭客指定的檔名存檔。一顆偽裝成小貓照片的炸彈,就這樣被直接送進了儲藏室,而且引信完好——因為那個資料夾本身允許執行 PHP 🔑


🟢 🟢 🟢

⏱️ 從被抓包到爆量攻擊,中間到底發生了什麼事

這起事件的發展速度,快到有點嚇人——駭客的反應往往比防禦者的修補速度更快。我們把時間軸攤開來看:

📅 時間點 🔍 發生什麼事 🌡️ 局勢解讀
2026 年 7 月初 Super Forms 開發團隊接獲漏洞通報 問題被發現,倒數計時正式開始
7 月 8 日 官方緊急釋出修復版本 6.3.314 防禦方先馳得點,反應速度算快
7 月 9 日 Wordfence 正式公開漏洞技術細節 提醒全球網站更新,但同時也把「說明書」交給了駭客
7 月 14 日 網路上開始出現大量自動化攻擊程式 漏洞公開僅 5 天就被武器化,駭客的速度比想像中更快
8 月 18 日 單日攻擊次數衝上最高峰,超過 4 萬次 整場競速賽的最高潮,火力全開
截至報告撰寫時 累計偵測超過 25 萬次攻擊紀錄 已經是駭客圈裡「廣泛利用」的熱門目標,不是小打小鬧

🩵 荷包試算:如果網站真的中了招,後續清理成本才是最痛的——請資安公司做鑑識與清除木馬,行情從幾千到數萬元台幣都有可能,比起現在花五分鐘點一下「立即更新」,這筆帳怎麼算都不划算 💸


🔶 🔶 🔶

😨 講白了,這串攻擊到底能對我的網站幹嘛?

跟大部分「有帳號密碼保護才安全」的直覺相反,這次的威脅模型完全不吃這一套。我們照事實的確定程度,一層一層拆給你看:

  • 🏮 ✅ 已確認事實:任何路人不用帳號密碼,就能直接把惡意檔案送進你的伺服器。攻擊者只要向 /wp-admin/admin-ajax.php 送出兩個請求,就能取得合法通行證並完成上傳,全程無需登入。
  • 🏮 ✅ 已確認事實:檔案上傳成功即等於伺服器淪陷。駭客可以透過這個檔案遠端下達系統指令(RCE,遠端程式碼執行),等於直接拿到你伺服器的最高控制權。
  • 🏮 🟡 合理推論:客戶個資與資料庫內容很可能被一併帶走。一旦網站被接管,駭客很有機會把資料庫裡的客戶個資、訂單紀錄整批打包帶走,甚至在網頁裡偷埋惡意連結,過陣子被 Google 標記成「危險網站」,商譽跟著一起受傷。
  • 🏮 🔴 假設情境:共享主機可能引發骨牌效應。如果你的主機是便宜的共享虛擬主機,且主機商沒把不同客戶的資料夾徹底隔開,駭客甚至有機會從你這個被攻破的網站,一路摸到主機上其他人的網站,造成連環感染。

💡 碎碎念:「遠端程式碼執行」聽起來很硬,其實就是「隔空下指令」
你可以把自己的網站想成一間無人商店,平常只有店主(也就是你)才有鑰匙可以進去上架商品。「遠端程式碼執行」講白了,就是駭客不用踏進店裡、不用拿到你的鑰匙,光是坐在自己家裡打幾行字,就能讓店裡的機器人乖乖照做——上架什麼、搬走什麼,甚至把整間店的電源開關交出去,全部隔空遙控完成 🔑


🟪 🟪 🟪

🎭 先別急著慌,你先搞清楚自己屬於哪一種情境

看到這裡如果你已經開始緊張,先深呼吸——不同身分的人,該做的事其實差很多,我們拆成幾種常見情境,你看看自己最接近哪一種。

🙋 我只是負責發文、管會員的網站小編

如果你平常的工作是寫文章、上架商品、回覆客服,完全不碰程式碼跟後台深層設定,那你要做的事其實很單純:確認版本、點更新,必要時先停用外掛。你不需要理解後面 DevOps 那段技術深潛,直接跳到下面「五分鐘無痛自救指南」照著做就好 💪

👨‍💻 我自己架站,手上也管著好幾個客戶的 WordPress

如果你身兼數職,同時顧著好幾個網站,光靠手動一個一個點後台更新太沒效率。這種情況下,用 WP-CLI 批次檢查所有站台的外掛版本會實際很多,後面的技術深潛篇會直接給你可以貼上就用的排查指令,建議拉到那一段 👇

🏢 我的網站有會員資料,或是有在線上收款

如果你的網站牽涉到會員個資或電商交易,這件事就不只是「更新一個外掛」這麼簡單了——它同時牽涉到資料外洩通報責任。除了更新跟排查之外,建議同步啟動內部的資安事件應變流程,把稽核日誌留存下來,之後不管是要跟保險公司對帳、還是配合調查,都用得上 🩺

🧑‍🔧 DevOps 小知識:為什麼「只停用外掛」有時候還不夠?
單純停用外掛能百分之百切斷「新的入侵」,這點沒問題。但如果你已經懷疑中招,光停用是不夠的——攻擊者留下的後門檔案跟外掛本身無關,就算你把外掛整個刪掉,後門依然會繼續運作。停用只能防止新的攻擊,已經進門的東西,還是得靠後面 Checklist 章節的排查手段才清得掉 🛡️

🚦 這個情境最容易被忽略:那個「裝完就再也沒管過」的表單外掛

🚨 高風險場景模擬:三年前架好的聯絡表單,就被遺忘在角落
一間台灣的中小企業,三年前架設官網時裝了 Super Forms 做聯絡表單,架好之後就再也沒點開過它的設定頁,也沒特別注意過版本更新。網站主觀認為「反正表單一直有在收信,應該沒事」。問題是外掛只要還處於「啟用」狀態,就算你幾乎不去後台管理它,攻擊者依然能透過這個漏洞悄悄埋炸彈 🆘

🚥 劃重點:「一直有在收信」不等於「沒有風險」,只要外掛還在啟用清單裡,攻擊者就有機可乘——這也是這次漏洞最容易被忽略的地方 🙅‍♂️


🟢 🟢 🟢

🛟 五分鐘無痛自救指南(電腦小白也能跟著做)

不管你屬於上面哪一種情境,這一段的步驟建議每個人都先跑一遍——不用懂程式,跟著畫面點就好。

  • 先去確認版本,去哪裡點?
    登入你的 WordPress 後台,點左側選單的「外掛(Plugins)」,再點「已安裝外掛(Installed Plugins)」。在整串清單裡找名字叫 Super Forms – Drag & Drop Form Builder 的那一項,它右側會標示目前的版本號碼 🔢
  • 看到版本號之後,怎麼判斷自己安不安全?
    如果版本號是 6.3.313 或更舊,代表你目前正暴露在風險裡;如果已經顯示 6.3.314 或更新,那就可以先鬆一口氣 ㊙️
  • 需不需要馬上更新?
    需要,而且優先程度排在今天所有代辦事項的第一位。建議先用備份工具(例如 UpdraftPlus)把整站與資料庫完整備份一次,再點「立即更新」,等它跑完,重新整理頁面確認版本號已經變成 6.3.314 以上即可 💪
  • 更新完就沒事了嗎?還有一步不能省。
    點開「使用者」選單的「所有使用者」,把角色欄位過濾成「管理員」,看看名單裡有沒有你完全不認識的帳號——特別是 Email 看起來像亂碼、或是註冊時間精確到「同一秒」的可疑帳號 🔍
  • 如果按了更新按鈕卻沒反應,或是根本不敢按怎麼辦?
    不需要自己硬著頭皮排除,直接把這篇報告網址轉傳給你的網站維護工程師、外包商,或是主機代管服務商,請他們優先處理這項更新,同時請他們順便檢查有沒有已經被入侵的跡象。找人幫忙是很正常的事 🙇
  • 如果因為相容性問題暫時真的沒辦法更新,該怎麼辦?
    既然這個漏洞完全靠「表單提交端點被匿名觸發」才會引爆,只要你把外掛直接點「停用(Deactivate)」,攻擊者的路徑就完全被截斷。等相容性問題解決後,再評估是否更新後重新啟用 🤔

🫂 新手求助:完全看不懂「後台」「Nonce」這些詞怎麼辦?
完全沒關係,這篇報告本來就不是要你變成資安專家。如果你連「已安裝外掛」這個選單都找不到,最快的方式就是把整篇報告網址直接轉傳給你的主機商客服或網站外包商,跟他們說「我這個外掛有嚴重漏洞(CVE-2026-14894),麻煩幫我更新並檢查」,多數台灣主機商都有提供這類技術支援管道 🤗

[55]


🔴 🔴 🔴

🔍 技術深潛篇:這串攻擊的程式碼底層到底哪裡壞掉了

接下來這段是給想知道「為什麼會這樣」的人看的——如果你只想知道怎麼補洞,上面那段其實已經夠用了。但如果你也好奇這種等級的漏洞底層長什麼樣子,我們繼續往下拆 ⚔️

評估指標 具體資訊
外掛名稱 Super Forms – Drag & Drop Form Builder
開發廠商 WebRehab
CVE 編號 CVE-2026-14894
CVSS 評分 9.8(Critical)
CVSS 向量 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
受影響版本 <= 6.3.313
安全版本 >= 6.3.314(2026-07-08 釋出)
漏洞類型 未限制危險檔案類型上傳(CWE-434)+未授權存取繞過

🧑‍🔧 DevOps 小知識:這是一個「雙重失守」的漏洞,不是單一疏漏
表單提交端點 super_submit_form 被註冊成 nopriv 的 AJAX handler,函式內完全沒有做 current_user_can() 權限檢查;唯一的防線是 Session Nonce(sf_nonce)檢查,但同一個外掛又開了 super_create_nonce 這個同樣 nopriv 的端點,會直接發放合法通行證給任何未授權訪客。單獨一個疏漏或許還能靠另一道防線擋住,兩個疏漏疊在一起,才是真正致命的地方 🎯

繞過身分驗證進入 super_submit_form 之後,程式在處理帶有 datauristringfiles 欄位時,會用 PHP 的 str_replace 把空格替換成加號,接著截掉逗號前綴,再呼叫 base64_decode() 解碼。解碼出來的二進位資料,完全沒有經過任何 MIME Type 魔法位元組檢查,也沒有副檔名白名單過濾,就直接拿攻擊者透過 value 參數傳入的檔名,跟 $GLOBALS[‘super_upload_dir’] 做字串拼接,然後呼叫 fopen()fwrite() 把檔案寫死在伺服器上。這一連串的疏漏,鋪成了未授權 RCE 的康莊大道 🟡(這段細節是根據 Wordfence 的分析報告,反推外掛原始碼邏輯所做的合理推論,並非官方原始碼公開內容) 🛣️


🟧 🟧 🟧

🎯 從埋雷到奪權,攻擊者只用了三個階段

整條攻擊鏈路門檻極低,低到很容易被寫成全自動的攻擊腳本,拆開來看邏輯其實相當工整 🕵️‍♀️

  • 🏮 第一階段:無驗證取得通行證。攻擊者向 /wp-admin/admin-ajax.php 發送 action=super_create_nonce 的 POST 請求,直接拿到一張合法的 Session Nonce,全程不需登入。
  • 🏮 第二階段:偽裝檔案,遞交惡意 Payload。拿著剛剛取得的通行證,再發送一個帶有 action=super_submit_form 的請求。惡意 PHP 腳本先被轉成 Base64 編碼,包裝成看起來像合法圖片格式的樣子(例如 data:image/gif;base64,…),同時把 value 參數(最終存下來的檔名)設成類似 Mushr00w_upl.php 的名字。
  • 🏮 第三階段:直接連線執行,完成接管。檔案寫入伺服器後,攻擊者只需要用瀏覽器直接連到該檔案的路徑,就能執行 Web Shell,取得遠端程式碼執行能力,全程無需任何登入權限。

🧑‍🔧 DevOps 小知識:為什麼駭客特別愛把後門埋在上傳目錄,而不是隨便找個地方?
上傳目錄(例如 wp-content/uploads/superforms/)通常不會出現在一般外掛清單或版本檢查的掃描範圍內,也不需要在資料庫裡被「啟用」,只要檔案存在,下一個 HTTP 請求就能直接觸發執行。對駭客來說,這裡是最不容易被一般管理員注意到、又能立刻取得執行權的黃金地段 🗝️

🟡 合理推論:由於檔案寫入路徑高度依賴未經淨化的 $value[‘value’] 參數與 $GLOBALS[‘super_upload_dir’],攻擊者很有可能結合路徑穿越手法(例如輸入 ../../../../wp-config.php 之類的路徑試圖覆寫關鍵檔案),把後門埋在上傳目錄以外、更難被發現的系統深處。🔴 假設情境:如果主機是權限控管鬆散、沒有正確設定 open_basedir 的共享主機,攻擊者理論上不只能摧毀當前站點,還可能讀到同一台主機上其他網站的資料庫密碼,讓整台實體主機的所有 WordPress 站點都陷入橫向感染的風險 😥


🔷 🔷 🔷

✅ 資安排查 Checklist:DevOps 該怎麼確認自己有沒有已經中招

如果你是自己架 VPS、同時管理多個 WordPress,這一節最重要的不是「背一條神奇迴圈指令」,而是先搞清楚自己到底是哪一種 WordPress 架構。搞錯架構,後面的批次指令就算語法完全正確,也可能處理錯對象。這一節會依序走過環境判斷 👉 盤點(版本比對)👉 Log 日誌排查 👉 後門檢查點 👉 加固檢查五個關卡,每一關都拆成「決策原理 👉 具體指令 👉 預期輸出範例 👉 分支判斷邏輯」四個段落,並分別涵蓋單一網站、獨立多站、Multisite 三種環境 📜

🔥 一句話先記住:「版本已升級到 6.3.314」代表你已經跨過這道漏洞的修補門檻,但不等於「網站歷史上從來沒有被摸過」。如果舊版本曾經對外公開超過一段時間,版本修補與入侵排查應該視為兩件不同的工作,缺一不可 🧯


🧭 🧭 🧭

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

決策原理:環境判斷錯誤會直接導致後續盤點、Log 排查、備份、更新全部跑偏——單一站、獨立多站、Multisite 三者的 WP-CLI 參數與資料範圍完全不同,必須先用官方指令確認,而不是憑印象猜 🤔

環境 典型結構 正確思路 常用 WP-CLI 定位方式
💠 單一網站 一個 WordPress 安裝 直接在該網站目錄操作 –path=”$WP_PATH”
❇️ 獨立多站 一台 VPS 上有多個完全獨立的 WordPress 每個 wp-config.php 都代表一個獨立安裝 –path=”$path”
✴️ Multisite 一個 WordPress Network,底下有多個 Site Plugin 檔案屬於整個 Network,不是每個 Site 一份 –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,並取得檔案實際擁有者,避免用單一 Linux 帳號跑遍所有網站造成權限錯亂 ⚙️

SEARCH_ROOT="/var/www"

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

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

預期輸出範例:

WordPress: /var/www/site-a/public_html (owner: site-a)
WordPress: /var/www/site-b/public_html (owner: site-b)

分支判斷邏輯:

  • ✔️ 每個路徑都能通過 👉 代表可以建立獨立多站 Inventory
  • 🟠 找到 wp-config.php 卻無法載入 WordPress 👉 先確認 PHP、檔案權限或網站路徑,不要直接更新
  • 🟡 同一個網站被找到多個 wp-config.php 👉 先人工確認哪個才是 Production,避免誤更新備份或 Staging 副本

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

決策原理:wp site list 是 Multisite 專用指令,WP-CLI 官方文件明確將它定義為列出 Multisite installation 中的 Sites,不能拿來掃描一台 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

分支判斷邏輯:能列出多個 Site 👉 進入 Multisite 流程;若不確定,可搭配檢查設定檔:

grep -E "MULTISITE|SUBDOMAIN_INSTALL|DOMAIN_CURRENT_SITE|PATH_CURRENT_SITE" "$WP_PATH/wp-config.php"

🧑‍🔧 DevOps 小知識:「獨立多站」像同一棟大樓裡有三家公司,各自有自己的門、帳冊與倉庫;Multisite 則比較像同一家公司底下有三個部門,外觀看起來都是「三個網站」,但資料庫、Plugin 檔案與更新邏輯完全不同 🏢


🔎 🔎 🔎

🔎 ㊁ 盤點(版本比對):先回答「哪些站裝了這款外掛、目前到底是哪一版?」

目前已知公開資料把 6.3.313 以下列入受影響範圍,6.3.314 為官方修補版本。這一節不建議把版本號寫死在腳本裡,而是讓 WP-CLI 直接跟 WordPress.org 官方資料比對,因為攻擊者取得控制權後,很可能手動竄改外掛檔案內的版號來規避掃描 🔍

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

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

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

預期輸出範例:

name: super-forms
status: active
version: 6.3.312
update: available
update_version: 6.3.314

分支判斷邏輯:

  • 🔴 6.3.313 或更舊 👉 視為受影響,進入後面備份/應急/修補流程
  • 🟢 6.3.314 或更新 👉 此漏洞的版本條件已解除,但若網站曾經使用過舊版,仍應繼續往下做 Log 與後門排查
  • 🟠 找不到 Plugin 👉 先確認是否已被移除、是否改了資料夾名稱,並確認不是查錯了網站路徑

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

SEARCH_ROOT="/var/www"

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

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

    version="$(sudo -u "$owner" -- wp --path="$path" \
        plugin get "super-forms" \
        --field=version 2>/dev/null || true)"

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

預期輸出範例:

Site: /var/www/site-a/public_html | Owner: site-a | Version: 6.3.314
Site: /var/www/site-b/public_html | Owner: site-b | Version: 6.3.312

分支判斷邏輯:這份結果就是你的第一張「風險地圖」——版本落後的站優先列入修補名單;已是最新版的站可以繼續進行歷史排查,確認舊版暴露期間有沒有留下痕跡 🗺️

🚨 注意:上面的 owner 是從檔案實際擁有者取得,而不是硬編碼固定帳號。所有路徑變數與檔名一律使用雙引號包覆,避免空格或特殊字元造成指令中斷 ⛓️‍💥

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

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

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

wp --path="$WP_PATH" plugin is-active "super-forms" --network
echo "Network activated exit code: $?"

分支判斷邏輯:Exit code 0 代表 Network Activated,處理範圍要以整個 Network 為單位;Exit code 1 則代表只在部分 Site 啟用,可用 –url=”$url” 逐一確認各站啟用狀態 🧭


🟥 🟥 🟥

📊 ㊂ Log 日誌排查特徵:找「路徑+時間+結果」的組合,不是看到 404 就緊張

決策原理:這個漏洞的利用特徵非常明顯(特定端點、特定參數),排查時真正有價值的不是找某個「神奇 IP」,而是確認是否曾經出現對應路徑、HTTP 方法與異常回應碼的組合 🎯

排查目標 異常特徵/關鍵字 潛在惡意行為分析
通行證濫用 action=super_create_nonce 的異常請求 來自單一 IP 短時間內大量索取通行證,通常是自動化攻擊腳本的前置動作
惡意檔案上傳 action=super_submit_form 且回應碼為 200 代表 Payload 已成功交付,檔案很可能已經寫入伺服器
已知後門檔名 Mushr00w_upl.php 的存取紀錄 若出現對這類檔名的 GET 請求,代表駭客正在嘗試連線執行後門

💠 單一網站:先找得到 Log,再做關鍵字與時間範圍搜尋

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

if [ -f "$LOG_FILE" ]; then
    grep -Ei "action=super_submit_form|Mushr00w_upl\.php|super_create_nonce" "$LOG_FILE" |
    tail -n 100
else
    echo "LOG NOT FOUND: $LOG_FILE"
fi

預期輸出範例:

... "POST /wp-admin/admin-ajax.php?action=super_create_nonce HTTP/1.1" 200 ...
... "POST /wp-admin/admin-ajax.php?action=super_submit_form HTTP/1.1" 200 ...

分支判斷邏輯:

  • 🟢 查無紀錄,或狀態碼為 403/404 👉 尚未遇襲,或已被 WAF 攔下,可繼續平順執行更新
  • 🟡 出現 super_create_nonce 但沒有後續 super_submit_form 👉 提高警覺,接著比對同一 IP 後續的請求紀錄
  • 🔴 super_submit_form 出現 200 回應 👉 高度懷疑已被成功利用,立刻跳到後面「已中招」流程,不要只做更新就以為沒事

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

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=super_submit_form|Mushr00w_upl\.php|super_create_nonce" "$log" 2>/dev/null || true)"

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

分支判斷邏輯:某個 Log 出現命中 👉 依檔名或 VirtualHost 設定回推是哪一站,把該站標記為需要進一步鑑識;全部沒有命中 👉 風險降低,但不能證明「絕對沒被摸過」,因為 Log 可能已輪替、遺失 ♻️

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

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

if [ -f "$LOG_FILE" ]; then
    grep -Ei "action=super_submit_form|Mushr00w_upl\.php|super_create_nonce" "$LOG_FILE" |
    tail -n 100
fi

分支判斷邏輯:找到可疑時間點後,再用 wp site list –field=url 取得 Network Site 清單,依 Log 中的 Host/URL 判斷哪個 Site 受到影響 🔗

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


🟣 🟣 🟣

🕳️ ㊃ 後門檢查點:Checksum 只能證明「官方檔案沒被改」,不能證明「網站沒有後門」

決策原理:這次漏洞的攻擊終點是把 PHP 後門直接寫進上傳目錄,這個目錄完全不受一般外掛版本檢查涵蓋,必須額外檢查上傳目錄、mu-plugins、近期異動檔案與使用者帳號 🕵️

💠 單一網站:先查專屬上傳目錄,再擴大到全站與帳號

WP_PATH="/var/www/example.com/public_html"
UPLOAD_DIR="$WP_PATH/wp-content/uploads/superforms"
MU_PLUGINS="$WP_PATH/wp-content/mu-plugins"

if [ -d "$UPLOAD_DIR" ]; then
    find "$UPLOAD_DIR" -type f -iname "*.php" -print
else
    echo "UPLOAD DIRECTORY NOT FOUND: $UPLOAD_DIR"
fi

if [ -d "$MU_PLUGINS" ]; then
    find "$MU_PLUGINS" -maxdepth 2 -type f -iname "*.php" -print
fi

find "$WP_PATH" -type f -name "*.php" -mtime -14 \
    -not -path "*/cache/*" -not -path "*/updraft/*" -print

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

預期輸出範例:若系統乾淨,前三段指令應無任何輸出;若遭駭,可能看到類似:

/var/www/example.com/public_html/wp-content/uploads/superforms/Mushr00w_upl.php

分支判斷邏輯:

  • 🔴 上傳目錄或 mu-plugins 出現任何 .php 檔案 👉 不要直接刪除,先保留檔案時間、權限、雜湊與副本供鑑識,並進入後面「已中招」流程
  • 🟡 全站近 14 天異動的 PHP 檔案清單裡出現不認識的檔名 👉 標記下來逐一確認,不要整批刪除
  • 🟢 使用者清單裡出現無法識別的 Email,尤其註冊時間彼此非常接近 👉 代表控制權可能已完全淪陷,必須立刻啟動資安事件應變程序

❇️ 獨立多站:每站各自檢查,結果要帶上網站路徑

SEARCH_ROOT="/var/www"

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

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

    hits="$(find "$upload_dir" -type f -iname "*.php" -print)"

    if [ -n "$hits" ]; then
        printf '\n===== SUSPICIOUS UPLOADS: %s (owner: %s) =====\n' "$path" "$owner"
        printf '%s\n' "$hits"
    fi
done

分支判斷邏輯:每個站獨立判斷,不要看到 A 站乾淨就把 B、C 站一起視為乾淨;找到命中的站優先進入事件調查,其餘站繼續走一般盤點流程 🧭

✴️ Multisite:mu-plugins 屬於 Network,一份檢查即可,帳號要分 Network/Site 兩層看

WP_PATH="/var/www/network/public_html"
MU_PLUGINS="$WP_PATH/wp-content/mu-plugins"

if [ -d "$MU_PLUGINS" ]; then
    find "$MU_PLUGINS" -maxdepth 2 -type f -iname "*.php" -print
fi

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

分支判斷邏輯:發現可疑 mu-plugins 檔案 👉 把整個 Network 都納入事件調查範圍;發現未知帳號後,再進一步確認它屬於哪個 Network/Site 層級的權限,不要看到帳號存在就直接刪除 🧾

🚨 Checksum 驗證不是完整的入侵鑑識。它只能驗證 WordPress.org 能提供 checksum 的外掛檔案,無法證明整個網站「一定沒有後門」。就算通過驗證,仍必須額外檢查上傳目錄、mu-plugins、使用者帳號與 Web Server Log,這幾項缺一不可 🔐


🔍 🔍 🔍

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

決策原理:這次漏洞之所以能造成 RCE,核心原因是「上傳目錄本身擁有執行 PHP 的權限」,因此加固不能只靠「更新外掛」單一動作,還要從伺服器層封鎖攻擊面 🔒

💠 單一網站:外掛 Checksum 驗證+刷新 Salts

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

wp --path="$WP_PATH" plugin verify-checksums "super-forms"
wp --path="$WP_PATH" core verify-checksums
wp --path="$WP_PATH" config shuffle-salts

預期輸出範例:

Success: Verified 1 of 1 plugins.

分支判斷邏輯:

  • 🟢 Verified 👉 目前安裝的外掛檔案與 WordPress.org 官方版本一致,可信度高
  • 🔴 Checksum mismatch 👉 不要忽略,代表檔案內容跟官方版本不同,應立即進一步做入侵鑑識
  • 🟡 Checksum 無法驗證 👉 可能是商業版外掛沒有官方 checksum 資料,可改用 diff -ur 跟原廠提供的乾淨 ZIP 檔比對

伺服器層防護(Nginx 範例,套用前先在 Staging 驗證,且注意規則要放在 PHP-FPM 主要處理規則之前):

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

❇️ 獨立多站:逐站驗證,結果保留路徑方便比對

SEARCH_ROOT="/var/www"

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

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

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

    sudo -u "$owner" -- wp --path="$path" plugin verify-checksums "super-forms"
done

分支判斷邏輯:每個網站獨立判斷,任何一站出現 mismatch,就先暫停該站的自動化排程,避免可疑檔案被排程覆寫掉,干擾後續鑑識 ⏸️

✴️ Multisite:Plugin 檔案共用,Checksum 也只驗證一次

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

wp --path="$WP_PATH" plugin verify-checksums "super-forms"
wp --path="$WP_PATH" core verify-checksums

🧑‍🔧 DevOps 小知識:建議同步檢查 php.inidisable_functions,考慮禁用 system、exec、shell_exec、passthru、eval,並確認 open_basedir 已限制在網站根目錄內,這樣即使萬一有後門被植入,實質破壞力也會被收斂不少 ⚙️

🫂 新手求助:如果不熟悉 WP-CLI 或 SSH 操作,可以直接參考 WP-CLI 官方文件的 plugin verify-checksums 指令頁面,或請你的主機商客服協助執行這一節的排查指令 🤗


🕵️ 🕵️ 🕵️

📋 ㊅ 速查表:三種架構的排查地圖

階段 💠 單一網站 ❇️ 獨立多站 ✴️ Multisite
① 環境判斷 wp core is-installed 逐站 wp-config.php wp site list
② 盤點(版本) plugin get “super-forms” 逐站 –path Network Plugin+–network
③ Log 排查 單一 Access Log 逐 VirtualHost Log 共用 Log+URL 比對
④ 後門檢查 uploads+mu-plugins+帳號 逐站 uploads 目錄 Network mu-plugins+Site 帳號
⑤ 加固檢查 Checksum+權限+WAF 逐站 Checksum Network Checksum 一次

🔥 核心原則:「批次」不代表「把所有東西塞進一條 Bash 迴圈」。真正安全的批次排查,是先判斷環境,再逐層縮小範圍,尤其多站 VPS 與 Multisite 必須分開處理 🎯

[56]


🟧 🟧 🟧

🛠️ 修不了就先擋:短期應急與正式修補三部曲

前一節解決的是「怎麼判斷」,這一節則進入真正的維運 Runbook。無論你屬於哪一種情境,強制流程都是同一套:判斷環境 👉 盤點 👉 備份 👉 Dry-run 👉 更新 👉 驗證,每個階段都必須完成,才能進入下一階段 🧹

階段 目的 核心動作
① 判斷環境 避免操作錯網站 單站/獨立多站/Multisite
② 盤點 知道誰受影響 版本、Plugin、Site Inventory
③ 備份 保留回復點 DB+必要網站檔案
④ Dry-run 先看更新會做什麼 –dry-run
⑤ 更新 關閉漏洞 升級至 6.3.314 或更新版本
⑥ 驗證 確認修補真的完成 版本+Checksum+功能+Log

🔴 🔴 🔴

🚨 ㊀ A. 已中招/疑似中招:先止血再修補

決策原理:一旦排查出現明確跡象(上傳目錄或 mu-plugins 可疑檔案、日誌出現 2xx 命中),優先目標是「切斷攻擊鏈+保留證據+隔離後門」,而不是立刻更新——更新可能會覆寫掉駭客留下的部分足跡,破壞鑑識證據 🧯

💠 單一網站:先建立事件證據,再止血

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

mkdir -p "$INCIDENT_DIR"

cat > "$INCIDENT_DIR/summary.txt" <<EOF
=== Incident collection ===
$(date -Is)
WP_PATH=$WP_PATH

$(wp --path="$WP_PATH" core version)

$(wp --path="$WP_PATH" plugin get "super-forms" \
    --fields=name,status,version,update,update_version)
EOF

wp --path="$WP_PATH" user list \
    --role=administrator \
    --fields=ID,user_login,user_email,display_name,user_registered \
    --format=csv \
    > "$INCIDENT_DIR/administrators.csv"

find "$WP_PATH/wp-content/uploads/superforms" -type f \
    -printf '%TY-%Tm-%Td %TH:%TM:%TS %u:%g %m %p\n' \
    2>/dev/null \
    > "$INCIDENT_DIR/uploads-inventory.txt"

tar -czvf "$INCIDENT_DIR/evidence_$(date +%F).tar.gz" \
    -C / \
    "var/log/nginx" \
    "var/www/html/wp-content"

echo "Incident evidence: $INCIDENT_DIR"

預期輸出範例:

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

分支判斷邏輯:證據已建立 👉 接續下方止血動作;如果是企業、電商或會員制網站,同步通知內部資安或維運負責人,並保留這份 Incident Directory 到受保護的位置 📦

止血:隔離可疑檔案(先隔離,不要直接刪除)、強制刷新安全性鹽值、重置管理員密碼:

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

mkdir -p "$QUARANTINE_DIR"

find "$WP_PATH/wp-content/uploads/superforms" -type f -iname "*.php" -print0 |
while IFS= read -r -d '' f; do
    printf 'Quarantine candidate: %s\n' "$f"
    chmod 000 -- "$f"
    mv -- "$f" "$QUARANTINE_DIR/"
done

wp --path="$WP_PATH" plugin deactivate "super-forms"
wp --path="$WP_PATH" config shuffle-salts

wp --path="$WP_PATH" user list --role=administrator --field=user_login |
while IFS= read -r login; do
    printf 'Resetting password for: %s\n' "$login"
    wp --path="$WP_PATH" user reset-password "$login" --skip-email
done

分支判斷邏輯:隔離與重置動作全部成功 👉 進入正式修補流程;任一步驟失敗(例如檔案權限不足) 👉 先解決權限問題,不要略過這一步直接升級外掛版本,否則同一個入侵路徑可能再次被利用 🔁

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

INCIDENT_PATH="/var/www/site-b/public_html"
INCIDENT_OWNER="$(stat -c '%U' -- "$INCIDENT_PATH/wp-config.php")"

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

分支判斷邏輯:確認路徑與擁有者正確 👉 對這一站套用單一網站流程中的證據保存與隔離指令;不要把整個 /var/www 當成單一網站處理 🧭

✴️ 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 "super-forms" \
    --fields=name,status,version,update,update_version

find "$WP_PATH/wp-content/mu-plugins" -type f \
    -printf '%TY-%Tm-%Td %TH:%TM:%TS %u:%g %m %p\n' \
    2>/dev/null

分支判斷邏輯:任一 Site 出現明確入侵跡象 👉 對整個 Network 執行證據保存、隔離與鹽值刷新;接著再逐一檢查各 Site 的管理員帳號是否有異常 🔍


🟡 🟡 🟡

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

🚨 短期應急 ≠ 正式修補。停用外掛、伺服器層防護規則都屬於緩衝措施,目標是縮短攻擊面暴露時間,真正的修補仍然是安裝 6.3.314 或更新版本 🧱

💠 單一網站:停用外掛,優先降低暴露面

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

wp --path="$WP_PATH" plugin deactivate "super-forms"

預期輸出範例:

Plugin 'super-forms' deactivated.
Success: Deactivated 1 of 1 plugins.

分支判斷邏輯:

  • 🟢 可以安全停用 👉 保留停用狀態,盡快排入「C. 正式修補」窗口
  • 🟠 業務無法承受表單整個停用 👉 改採下方伺服器層防護規則,並儘速安排升級

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

INCIDENT_PATH="/var/www/site-b/public_html"
INCIDENT_OWNER="$(stat -c '%U' -- "$INCIDENT_PATH/wp-config.php")"

sudo -u "$INCIDENT_OWNER" -- wp --path="$INCIDENT_PATH" plugin deactivate "super-forms"

分支判斷邏輯:這個指令只影響 Site B,不應連其他站一起停掉;建議先跑盤點迴圈篩出版本落後的站清單,再針對清單逐一停用 📋

✴️ Multisite:先確認 Plugin 是否 Network Activated,再決定停用範圍

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

if wp --path="$WP_PATH" plugin is-active "super-forms" --network; then
    echo "Plugin is Network Activated."
else
    echo "Plugin is not Network Activated."
fi

伺服器層防護規則(Nginx 範例,套用前請先在 Staging 驗證):

location ~* "/wp-admin/admin-ajax\.php" {
    if ($arg_action = "super_submit_form") {
        return 403;
    }
    if ($arg_action = "super_create_nonce") {
        return 403;
    }
}

為什麼停用就能擋?這條攻擊鏈完全依賴表單提交端點才能觸發,停用外掛能百分之百截斷觸發鏈路,是目前最乾淨、風險最低的短期緩解手段,但不能取代正式升級 🏎️


🚀 🚀 🚀

㊂ C. 正式修補:強制流程「判斷環境 👉 盤點 👉 備份 👉 Dry-run 👉 更新 👉 驗證」

🩵 荷包試算:這幾道防線要花多少錢?伺服器層設定與停用外掛本質上都是免費操作,只要有 SSH 權限就能自己動手。真正會花到錢的地方,通常是「已經中招之後」——請資安顧問做鑑識、清除後門的工時,這筆帳,怎麼算都是現在就花時間走完這套 Runbook 比較划算 💸

① 判斷環境(再次確認)

回到前面「㊀ 環境判斷」執行判斷,確認自己屬於哪一種架構後再繼續,不要在還沒確認架構前就執行下面任何批次指令。

② 盤點(Inventory)

沿用前面「㊁ 盤點(版本比對)」的三段指令,先確認哪些站真正安裝了受影響版本,把結果列成清單,不要在第一個迴圈裡直接更新。

③ 備份(Backup)

決策原理:外掛更新主要改的是 Plugin 檔案,但網站內容與設定仍可能需要回復,因此至少要有資料庫備份,備份檔案統一集中放在 ~/wp-security-backup 💾

💠 單一網站:

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

tar -czf \
    "$BACKUP_DIR/example-com_wp-content_pre_superforms_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_superforms_update_20260904_123000.sql'.
Backup completed:
/home/admin/wp-security-backup

分支判斷邏輯:SQL 與檔案備份都存在 👉 進 Dry-run;任一備份失敗 👉 停止,不進入正式更新,先排除磁碟空間或權限問題 🛑

❇️ 獨立多站:每一個 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")"
    owner="$(stat -c '%U' -- "$config")"
    site_name="$(basename -- "$path")"
    db_file="$BACKUP_ROOT/${site_name}_pre_superforms_update_${STAMP}.sql"

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

    echo "=== Backup: $path ==="

    sudo -u "$owner" -- wp --path="$path" db export "$db_file"

    printf 'Database backup: %s\n' "$db_file"
done

分支判斷邏輯:某一站備份失敗,就把該站標記為未完成,不要因為其他站成功就直接進入批次更新 ⛔

✴️ Multisite:Network Database 不要重複 dump N 次

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

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

🚨 不要在 Multisite 的每個 URL 上重複執行整個 wp db export對 Network 執行時,匯出的就是整個 installation 使用的資料庫,重複執行只是浪費空間 🗄️

④ Dry-run(正式修改前先預覽)

💠 單一網站:

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

wp --path="$WP_PATH" plugin update \
    "super-forms" \
    --dry-run

預期輸出範例:

Plugin super-forms 6.3.312 will be updated to 6.3.314.

分支判斷邏輯:看到目標版本 👉 可以正式更新;顯示沒有可用更新 👉 回頭確認目前版本與更新來源,可能是主機對外連線被防火牆阻擋,不可強制寫入,需改由人工手動上傳 ZIP 檔覆蓋 🔮

❇️ 獨立多站:每站各自 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")"
    owner="$(stat -c '%U' -- "$config")"

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

    version="$(sudo -u "$owner" -- wp --path="$path" \
        plugin get "super-forms" \
        --field=version 2>/dev/null || true)"

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

        sudo -u "$owner" -- wp --path="$path" plugin update \
            "super-forms" \
            --dry-run
    fi
done

✴️ Multisite:Plugin 檔案只 Dry-run 一次

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

wp --path="$WP_PATH" plugin update \
    "super-forms" \
    --dry-run

⑤ 更新(確認備份與 Dry-run 都 OK 後才真正修改)

💠 單一網站:停用 👉 更新 👉 啟用

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

wp --path="$WP_PATH" plugin deactivate "super-forms"
wp --path="$WP_PATH" plugin update "super-forms"
wp --path="$WP_PATH" plugin activate "super-forms"

預期輸出範例:

Enabling Maintenance mode...
Downloading update from https://downloads.wordpress.org/plugin/super-forms.6.3.314.zip...
Unpacking the update...
Installing the latest version...
Removing the old version of the plugin...
Plugin updated successfully.
Disabling Maintenance mode...
Success: Updated 1 of 1 plugins.

分支判斷邏輯:更新成功 👉 立即進版本驗證;更新失敗 👉 不要反覆執行,先查看錯誤訊息、確認檔案權限與磁碟空間是否足夠 ⚠️

❇️ 獨立多站:一站一個完整閉環

WP_PATH="/var/www/site-a/public_html"
OWNER="$(stat -c '%U' -- "$WP_PATH/wp-config.php")"

sudo -u "$OWNER" -- wp --path="$WP_PATH" plugin deactivate "super-forms"
sudo -u "$OWNER" -- wp --path="$WP_PATH" plugin update "super-forms"
sudo -u "$OWNER" -- wp --path="$WP_PATH" plugin activate "super-forms"

sudo -u "$OWNER" -- wp --path="$WP_PATH" plugin get "super-forms" \
    --fields=name,status,version,update,update_version

🧑‍🔧 多站環境的實務原則:一次只處理一個網站,至少在第一批部署時如此。先完成一站的「備份 👉 更新 👉 驗證」,確認沒有異常,再繼續下一站,這比一個巨大 Bash 迴圈從頭跑到尾更容易控制風險 🎛️

✴️ Multisite:Plugin 檔案只更新一次,再逐 Site 驗證

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

wp --path="$WP_PATH" plugin deactivate "super-forms" --network
wp --path="$WP_PATH" plugin update "super-forms"
wp --path="$WP_PATH" plugin activate "super-forms" --network

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 "super-forms" \
        --fields=name,status,version
done

分支判斷邏輯:Plugin 檔案版本只需要更新一次;Site 層則逐一確認是否正常載入與啟用,任一 Site 出現錯誤 👉 暫停批次流程,先處理該 Site 🛑

⑥ 驗證(更新成功 ≠ 工作完成)

① 版本驗證

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

② Plugin Checksum 驗證

wp --path="$WP_PATH" plugin verify-checksums "super-forms"

③ WordPress Core Checksum

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

④ 前後台功能與最後一輪 Log 複查

  • ☑️ 前台首頁可以正常開啟
  • ☑️ 後台可以正常登入
  • ☑️ 含檔案上傳欄位的表單可以正常送出,且無 500 錯誤
  • ☑️ PHP error log 沒有突然出現大量 Fatal Error
LOG_FILE="/var/log/nginx/access.log"
WP_PATH="/var/www/example.com/public_html"

if [ -f "$LOG_FILE" ]; then
    grep -Ei "action=super_submit_form|action=super_create_nonce" "$LOG_FILE" |
    tail -n 50
fi

find "$WP_PATH/wp-content/uploads/superforms" -type f -iname "*.php" -print

分支判斷邏輯:更新後仍持續出現可疑請求,或上傳目錄再度出現新的 PHP 檔案 👉 不要當成「已修好所以沒事」,應回到「A. 已中招」流程重新處理 🔁

🔥 一句話總結:真正安全的批次維運,是先 Inventory,再 Backup,再 Dry-run,確認無誤後才逐批 Update,最後 Verify。這套「判斷環境 👉 盤點 👉 備份 👉 Dry-run 👉 更新 👉 驗證」的閉環,換成下一次別的外掛、別的 CVE,依然可以直接套用 🦸


🏁 🏁 🏁

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

評估維度 分數 一句話評語
漏洞嚴重程度(越高代表風險越大) 9.8 不用登入就能上傳後門,是最高等級的未授權 RCE
官方應變速度 7 從通報到修補不到一週,反應算及格
一般站長自救可行性 9 點更新按鈕就能解決,操作門檻不高但急迫度極高
DevOps 技術文件完整度 8 攻擊鏈路與伺服器層緩解設定齊全,方便直接落地部署
🏆 加權綜合建議 立即行動 最終建議:【今天內完成更新,不要拖到明天】

🔥 這起事件給了所有網站主一個提醒:漏洞不見得都躲在複雜的核心系統裡,有時候反而藏在一個「你以為只是收收表單、不會有事」的日常功能背後。與其等哪天真的出事才發現不對勁,不如現在就花五分鐘,打開後台看一眼版本號碼 🔢


📎 📎 📎

📚 參考資料與延伸資源

列印本文 [65] 👨‍👩‍👧‍👦 23 次瀏覽