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

超商置物櫃密碼鎖壞了?Divi Ajax Filter外掛9.8分致命漏洞拆解

內容目錄

🔐 超商置物櫃的密碼鎖壞了?拆解WordPress外掛Divi Ajax Filter漏洞

🔥 懶人包:
知名 Divi 佈景主題生態系裡的篩選外掛「Divi Ajax Filter」,被抓到一個不用登入、不用點任何東西,光靠一組網址參數就能讀走你網站保險箱鑰匙的本地檔案包含(LFI)漏洞,編號 CVE-2026-11613,CVSS 危險分數 9.8(滿分 10)。更麻煩的是,這種漏洞有機會被「借刀殺人」升級成整台伺服器被遠端遙控。如果你的網站有裝這款外掛,版本號還停在 5.1.2 或更早,這篇文章值得你放下手邊的事,花五分鐘看完 🚨

💡 碎碎念:你知道嗎,WordPress 有一個網址從頭到尾都是「故意」不用登入就能打開的
那個網址叫 admin-ajax.php,聽起來像後台專用,但其實 WordPress 官方一開始設計它的時候,就刻意保留了「訪客也能觸發」的通道——畢竟像是前台的搜尋建議、購物車數量更新,這些功能總不能要求使用者先登入才能用吧。這個「刻意留一道後門給訪客」的設計初衷,原本立意良好,卻也成了這次漏洞之所以能「不用登入就攻擊」的先天地基 🫨

🌐 前往 Patchstack 漏洞資料庫查看原始紀錄 [27]

[28]


🟦 🟦 🟦

🗝️ 先講人話:這個漏洞到底在幹嘛?

你有沒有用過超商那種可以刷條碼、輸入取件密碼就能自動打開對應櫃格的智慧型置物櫃?你在網路上下訂單,系統給你一組取件密碼,你走到機器前輸入密碼「A03」,機台就乖乖打開 A03 格子讓你拿走包裹。這整套機制的前提,是機台會嚴格檢查你輸入的密碼「只能對應到它自己管理範圍內的格子」,絕對不會讓你輸入什麼奇怪指令就跑去開別人家的門 🧋

Divi Ajax Filter 這款外掛,做的事情有點類似:當訪客在你網站上使用商品篩選、文章分類篩選功能時,它會依照訪客選的條件,去讀取對應的「顯示版型」檔案,決定畫面要長怎樣。這本來是個很貼心的設計,讓網站主可以自訂篩選結果的排版樣式 🎨

問題出在,這台「置物櫃機台」在讀取版型檔案之前,完全沒有檢查你輸入的「格子編號」到底合不合理。如果有心人士不輸入正常的格子編號,而是刻意打一串像 ../../../../etc/passwd 這種「往上爬好幾層樓、再鑽進機房控制室」的怪異路徑,機台居然會照單全收,乖乖把主機裡最核心的系統設定檔、甚至是網站的資料庫連線密碼,原封不動地吐出來給對方看 🙅‍♂️

在資安圈,這種「沒檢查請求路徑合不合理,就把伺服器上不該碰的檔案讀出來」的行為,有個專門的名字——本地檔案包含(Local File Inclusion,簡稱 LFI)。這次事件的關鍵,就是外掛在處理一個叫做 custom_loop_template 的參數時,沒有做任何路徑檢查與白名單限制,直接把訪客給的內容拿去餵給 PHP 的檔案載入函式 🏨

💡 碎碎念:為什麼 LFI 這種老掉牙的攻擊手法,過了二十年還是層出不窮?
講到「讀取任意檔案」,很多人第一反應是想到它的表親——遠端檔案包含(RFI),也就是直接叫伺服器去下載執行網路上某個惡意網址的程式碼。不過自從 2006 年 PHP 5.2 版本問世後,官方就把允許載入遠端網址的設定 allow_url_include 預設關閉了,讓純種的 RFI 幾乎絕跡。但 LFI 完全不吃這一套設定,因為它讀的是「本來就已經躺在你主機硬碟上」的檔案,跟網路連線設定一點關係都沒有——這也是為什麼時至 2026 年,LFI 依然是資安圈的常客 🌠

[29]


🟢 🟢 🟢

📅 短短幾天內,發生了什麼事

這起事件的節奏其實不算慢,我們把時間軸攤開來看看,你會發現「發現漏洞」跟「官方補完洞」中間其實隔不了幾天,但危機感一點都沒有因此降低:

時間點 發生了什麼事
2026-09-03 資安研究員 h0xilo 發現並公開這個漏洞的存在
近日內 Wordfence、Patchstack 等資安情報單位相繼發布嚴重警訊,並將危險分數評為 9.8 分(滿分10)
✅ 已確認 開發商 Divi Engine 反應算快,已釋出修復版本 5.1.3
🟡 合理推論 雖然目前沒有看到大規模攻擊災情,但漏洞細節既然已經曝光,多半有駭客組織正在把它寫成自動化掃描腳本,這扇沒上鎖的門,隨時可能被推開

值得一提的是,這次的漏洞觸發完全不需要任何登入權限,也不需要受害者做任何動作(例如點擊釣魚連結)——攻擊者只要對著你網站標準的篩選功能,發一個帶著怪異參數的請求就好,這種「零互動、零門檻」的組合,正是它拿到 9.8 分的原因 ⚠️

[30]


🔶 🔶 🔶

😨 如果中招,我的網站會被拖到多深的水?

我們照事情確定的程度,一層一層拆給你看,而不是一股腦地把最壞情況丟出來嚇你:

  • ✅ 已確認事實:核心機密被看光光。攻擊者最容易得手的目標,是讀走你網站的 wp-config.php 檔案。這裡面白紙黑字寫著資料庫的帳號密碼,等於直接把備用鑰匙交到陌生人手上。
  • 🟡 合理推論:從「偷看」升級成「整台接管」。有經驗的攻擊者通常不會滿足於只偷看檔案。他們會利用一招叫「日誌投毒(Log Poisoning)」的手法:先故意讓伺服器的訪客紀錄檔裡混入一段惡意程式碼,接著再利用這個 LFI 漏洞去讀取那份紀錄檔。PHP 引擎一旦解析到紀錄檔裡的惡意內容,會誤以為那是正常程式碼並直接執行,網站就這樣被完全遙控。
  • 🔴 假設情境:無辜訪客一起遭殃。網站一旦淪陷,駭客可能會偷偷在頁面裡塞進惡意廣告或釣魚連結,把不知情的訪客導向詐騙網站。這不只會讓 Google 把你的網站列入黑名單,多年累積的品牌信任感也會一夕崩盤。

💡 碎碎念:wp-config.php 其實是整個 WordPress 的「靈魂核心」
這個檔案除了資料庫帳密,還藏著一組叫做「鹽值(Salts)」的加密鑰匙,專門用來驗證登入狀態。一旦這串鹽值外洩,攻擊者甚至能偽造出「已登入管理員」的身分——這代表就算你事後緊急改了密碼,駭客照樣能像走自家廚房一樣,大搖大擺地晃進你的後台 😥

[31]


🟪 🟪 🟪

🧭 先別自己嚇自己:看看你屬於哪一種身分

資安新聞讀起來總是很嚇人,但不同身分的人該做的事情差很多,先對號入座,看看自己接下來要花多少心力:

🙋 我只是自己顧一個小網站,平常寫寫文章、賣點東西

如果你的網站用了 Divi 主題系列,順手裝了這個篩選外掛,那你要做的事情其實很單純:確認版本、點更新,五分鐘搞定,不用花半毛錢,也幾乎不會影響網站外觀。適合度:高,但要今天就做 💯

👨‍💻 我自己接案架站,手上管著好幾個客戶的 Divi 網站

如果你身兼數職,同時顧著十幾二十個客戶站台,一個一個手動點後台太沒效率了。這時候用 WP-CLI 搭配批次腳本逐站巡查,會實際很多——後面的技術深潛篇會直接給你可以複製貼上的完整指令。適合度:中,需要花點時間做批次盤點 ⚙️

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

如果你的網站牽涉到會員個資或電商交易,這件事就不只是「更新一個外掛」這麼簡單——一旦被證實資料曾經暴露,企業面對的往往不只是商譽受損,還有主管機關對資料外洩通報的裁罰責任。除了更新之外,建議同步啟動內部的資安應變流程,把稽核紀錄留存下來備查。適合度:低,建議走完整套排查流程 🚦

🩵 荷包試算:這次修補完全不用花錢,但「不修補」代價可能很高
更新這款外掛本身是免費動作,只要點幾下滑鼠。真正會燒錢的地方,是「已經中招之後」才處理——請資安顧問做鑑識、清除後門的工時費用,或是網站被搜尋引擎列入警示名單後重建流量的成本,這筆帳怎麼算,都是現在花五分鐘更新比較划算 💸

🚨 高風險場景模擬:三年前架好就沒人管過的婚禮工作室網站
一間台灣的婚禮顧問工作室,三年前請人用 Divi 主題架了官網,順手裝了這款篩選外掛讓客人依風格挑選案例照片。網站上線後案子接不完,官網從此再也沒人登入過後台,更別說點過任何一次更新。問題是,外掛只要還處於「啟用」狀態,就算完全沒人使用它的篩選功能,攻擊者依然能透過這個 LFI 漏洞直接讀走 wp-config.php,網站隨時可能在店主毫無察覺的情況下,被種下後門變成廣告跳板 🆘

🫂 新手求助:完全看不懂「外掛」「後台」是什麼也沒關係
這篇報告的目的從來就不是要你變成資安專家。如果你連「已安裝外掛」這個選單在哪裡都找不到,最快的方式就是把這篇文章的網址直接轉傳給你的網站建置商、外包工程師或主機商客服,跟他們說「我這個 Divi Ajax Filter 外掛有嚴重漏洞,麻煩幫我確認並更新」,多數台灣主機商都有提供這類技術支援管道,這件事本來就不該一個人扛 🤗


🟢 🟢 🟢

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

不管你屬於上面哪一種身分,這幾個步驟建議每個人都親自跑一遍,跟著畫面點滑鼠就好,完全不需要碰到任何程式碼:

  • 先去哪裡查?
    登入你的 WordPress 網站後台,點左側黑色選單的「外掛」👉「已安裝的外掛」,會出現一整串你網站上目前裝著的所有外掛清單。
  • 看什麼欄位?
    在清單裡找名字叫「Divi Ajax Filter」的那一項,它的名稱下方或旁邊會標示目前安裝的版本號碼,通常是像「5.1.2」這樣一串數字。
  • 怎麼判斷安不安全?
    如果版本號是 5.1.2 或更舊的數字(例如 5.1.1、5.0.0),代表你目前正暴露在風險裡;如果已經顯示 5.1.3 或更新,可以先鬆一口氣。判斷標準就是這一個數字。
  • 該怎麼處理?
    如果版本過舊,直接點外掛名稱下方的「立即更新」按鈕,等它跑完,重新整理頁面確認版本號已經變成 5.1.3 以上即可。這種專注於安全性的小版本更新,通常不會影響網站外觀,但保險起見,建議先用備份外掛做一次完整備份再按下更新。
  • 找不到選單、不敢按怎麼辦?
    如果完全找不到「外掛」選單,或是擔心按了更新會把網站弄壞,這完全沒關係——直接把這篇文章轉傳給幫你維護網站的工程師、接案設計公司或主機商客服,請他們代為評估與處理即可,這不是你的問題,找人幫忙很正常。

[32]


🧑‍🔧 🧑‍🔧 🧑‍🔧

🔬 第二部分:技術深潛篇——寫給 DevOps 與自架玩家看的完整 SOP

接下來這段是給想搞懂「為什麼會這樣」、以及需要對多個站台批次排查的技術人員看的。如果你只想確認自己有沒有中招,上面那段其實已經夠用了,但如果你也好奇這個漏洞底層長什麼樣子,我們繼續往下拆 ⚔️

🧩 這串程式碼到底哪裡沒守住

評估項目 具體資訊
外掛名稱 Divi Ajax Filter(Divi Engine 生態系篩選外掛)
CVE 編號 CVE-2026-11613
CVSS 3.1 評分 9.8(Critical)
CVSS 向量 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
漏洞分類 CWE-98(檔案包含函式的檔名控制不當),也就是本地檔案包含(LFI)
受影響版本 <= 5.1.2
安全版本 >= 5.1.3
觸發條件 參數 loop_templates=custom-template 搭配 custom_loop_template 帶入路徑穿越字元

🧑‍🔧 DevOps 小知識:問題出在「只給格式,不給白名單」
根據目前掌握的漏洞資訊,外掛在處理前台 AJAX 篩選請求時,會把訪客提交的 custom_loop_template 參數,直接原封不動地丟進 PHP 的檔案載入函式(例如 includerequire),中間沒有經過任何路徑正規化或白名單過濾。這代表攻擊者只要在參數裡塞入一長串 ../,就能逃出網站根目錄的邊界,直達伺服器上任意一個 PHP 引擎讀得到的檔案。至於官方 5.1.3 版本具體改了哪些程式碼細節,目前公開的漏洞情報並未附上原始碼差異比對,這部分屬於【❓待確認】,僅能從修補後行為反推:官方應是強制把版型載入的基礎路徑鎖定在合法範圍內,剝奪了外部參數指定絕對路徑的權利 🔧

🎯 從偷看到接管,中間可能只隔一個小動作

分析一個漏洞的威脅程度,不能只看 CVSS 分數,更要拆解它完整的攻擊鏈路能走多遠。這次的攻擊門檻低得令人擔憂:

  • ✅ 已確認的影響(任意檔案讀取):攻擊者完全不需要登入、也不用受害者做任何互動,只要對著網站標準的 admin-ajax.php 端點發一組帶特定參數的請求,就能讀走 wp-config.php。由於多數 WAF 預設不會全面阻擋這個端點的流量,攻擊行為很容易隱藏在正常網站流量之中。
  • 🟡 合理推論(升級為遠端程式碼執行):在多數常見的 PHP-FPM 或 mod_php 環境下,LFI 很少只停留在「讀取」階段。攻擊者可能會先發一個帶有惡意 PHP 指令的 User-Agent 標頭給你的網站,Nginx 或 Apache 會傻傻地把這串惡意字串寫進 access.log。接著攻擊者再利用這個 LFI 漏洞去包含(include)那份日誌檔——PHP 引擎解析到日誌裡的惡意字串時,會誤判為合法程式碼並直接執行,瞬間完成伺服器層級的全面接管。
  • 🔴 假設情境(橫向擴散):若這個 WordPress 站點跟企業內部其他系統共用同一台主機、又沒做好權限隔離,攻擊者可能藉此讀到其他使用者的 SSH 金鑰或雲端服務憑證,把這個網站當成跳板,進一步入侵內網。

[33]


🔍 🔍 🔍

📋 資安排查 Checklist:五個關卡,逐一過

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

面對這種零時差等級的高危漏洞,最怕的不是不知道怎麼修,而是「架構搞錯,指令用對地方卻處理錯對象」。以下流程嚴格依照判斷環境 👉 盤點 👉 Log排查 👉 後門檢查 👉 加固展開,並分別涵蓋「💠單一網站」「❇️獨立多站(一台主機裡有好幾個各自獨立的 wp-config.php)」「✴️Multisite(多站網路)」三種完全不同的架構。

🧑‍🔧 DevOps 小知識:先搞清楚地圖,再決定走哪條路
「一台主機上住著 10 個各自獨立的 WordPress」跟「一個 Multisite 網路裡有 10 個子站」看起來很像,其實是兩種完全不同的架構。前者要逐一找出各站的 wp-config.php;後者外掛檔案是整個網路共用的,處理邏輯完全不同。搞錯架構,後面所有批次指令都可能處理到錯誤的對象 🔧

🧭 ㊀ 環境判斷:你在管的是一個網站,還是一整台伺服器?

決策原理:環境判斷錯誤會直接讓後續的盤點、備份、更新全部跑偏——三種架構的處理範圍完全不同,必須先由系統層級確認,而不是憑印象猜 🫤

cat << 'EOF' > wp_env_check.sh
#!/bin/bash
WP_PATH="${1:-/var/www/html}"

if [ ! -f "$WP_PATH/wp-config.php" ]; then
    echo "❌ 找不到 wp-config.php,請確認輸入的根目錄路徑是否正確。"
    exit 1
fi

OWNER="$(stat -c '%U' -- "$WP_PATH/wp-config.php")"
IS_MULTISITE="$(grep -i "WP_ALLOW_MULTISITE" -- "$WP_PATH/wp-config.php" | grep -i "true")"
IS_NETWORK="$(sudo -u "$OWNER" -- wp core is-installed --network --path="$WP_PATH" 2>/dev/null)"

if [ -n "$IS_MULTISITE" ] || [ "$IS_NETWORK" = "1" ]; then
    echo "✴️ 偵測結果:這是一個 WordPress Multisite(Network)環境,外掛檔案為整個網路共用。"
else
    PARENT_DIR="$(dirname -- "$WP_PATH")"
    OTHER_SITES="$(find "$PARENT_DIR" -mindepth 2 -maxdepth 4 -name "wp-config.php" 2>/dev/null | wc -l)"

    if [ "$OTHER_SITES" -gt 1 ]; then
        echo "❇️ 偵測結果:這是一台託管多個「獨立網站」的伺服器(共找到 $OTHER_SITES 個獨立站點)。"
    else
        echo "💠 偵測結果:這是一個標準的「單一網站」環境。"
    fi
fi
EOF
chmod +x wp_env_check.sh
./wp_env_check.sh "/var/www/html"

預期輸出範例:

💠 偵測結果:這是一個標準的「單一網站」環境。

分支判斷邏輯:

  • ⭐ 若輸出為 💠 單一網站,後續直接套用單一網站對應的指令即可,不用考慮跨站影響。
  • ⭐ 若輸出為 ❇️ 獨立多站,後續所有指令必須搭配迴圈動態抓取各站的檔案擁有者身分逐站處理,切忌用 root 身分強制執行,以免破壞 PHP-FPM 的執行權限。
  • ⭐ 若輸出為 ✴️ Multisite,需理解外掛實體檔案只需更新一次,套用到啟用/停用的指令則要加上 –network 參數。

🔎 ㊁ 盤點(版本比對):先搞清楚哪些站裝了它、目前是哪一版

決策原理:不建議在腳本裡寫死要比對的版本號,因為漏洞情資可能持續更新。正確做法是讓 WP-CLI 直接查詢本機安裝狀態,並回報是否有官方推送的可用更新 🔢

# 💠 單一網站 & ✴️ Multisite(外掛實體共用,盤點指令相同)
WP_PATH="/var/www/html"
OWNER="$(stat -c '%U' -- "$WP_PATH/wp-config.php")"

sudo -u "$OWNER" -- wp --path="$WP_PATH" plugin list \
    --name="divi-ajax-filter" \
    --format=csv \
    --fields=name,version,update,update_version

# ❇️ 獨立多站(動態抓取伺服器內所有站點路徑與擁有者進行迴圈掃描)
SEARCH_ROOT="/var/www"

echo "開始盤點伺服器內所有 WordPress 站點的 Divi Ajax Filter 狀態..."

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

    printf '🔎 檢查站點:%s(使用者:%s)\n' "$site_path" "$owner"

    sudo -u "$owner" -- wp --path="$site_path" plugin list \
        --name="divi-ajax-filter" \
        --format=csv \
        --fields=name,version,update,update_version \
        || echo "此站點未安裝該外掛,或讀取失敗。"

    echo "---------------------------------------------------"
done

預期輸出範例:

name version update update_version
divi-ajax-filter 5.1.2 available 5.1.3

分支判斷邏輯:

  • ⭐ 輸出裡沒出現 divi-ajax-filter:代表該站點根本沒裝這款外掛,暫無相關風險。
  • ⭐ 版本為 5.1.2 或更舊,且 update 欄位顯示 available:屬於高風險站點,必須立刻進入下一階段的 Log 排查與備份更新。
  • ⭐ 若無法確認 update_version(例如官方情資尚未同步),採保守防禦原則:只要目前版本小於等於 5.1.2,一律視為危險,優先處理。

📊 ㊂ Log 日誌排查:找出「觸發條件+目錄穿越特徵」的組合

決策原理:LFI 攻擊一定會在伺服器的存取日誌裡留下痕跡。透過正規表示式抓出同時包含觸發參數 loop_templates=custom-template 與目錄穿越字元(如 ../ 或編碼後的 %2e%2e%2f)的請求,可以快速判斷網站是否已被實際攻擊,或只是處於自動化掃描階段 🫆

# 💠單一網站 / ❇️獨立多站 / ✴️Multisite(日誌通常在伺服器層級共用)
# 請依實際主機環境調整路徑(Apache 常見於 /var/log/apache2/)
shopt -s nullglob
LOG_FILES=(/var/log/nginx/access.log*)
shopt -u nullglob

if [ "${#LOG_FILES[@]}" -eq 0 ]; then
    echo "❌ 找不到符合的日誌檔案,請確認伺服器日誌實際存放路徑。"
else
    echo "正在掃描 LFI 目錄穿越攻擊特徵..."
    zgrep -Ei "loop_templates=custom-template" -- "${LOG_FILES[@]}" |
        grep -Ei "custom_loop_template=(%2e|\.|%2f|/)+(etc|windows|boot)"

    echo "正在掃描 Log Poisoning 攻擊特徵..."
    zgrep -Ei "loop_templates=custom-template" -- "${LOG_FILES[@]}" |
        grep -Ei "custom_loop_template=.*(access\.log|error\.log|environ|auth\.log|fd/)"
fi

預期輸出範例:

192.168.1.100 - - [05/Sep/2026:10:00:00 +0800] "GET /wp-admin/admin-ajax.php?action=divi_filter&loop_templates=custom-template&custom_loop_template=../../../../../etc/passwd HTTP/1.1" 200 4562 "-" "Mozilla/5.0"

分支判斷邏輯:

  • ⭐ 完全沒有任何輸出:目前尚未被攻擊或掃描器命中,可以直接跳到下方的「正式修補」流程。
  • ⭐ 出現 HTTP 狀態碼 200 的異常紀錄:高度懷疑攻擊已經成功執行,這時絕對不能直接貿然更新外掛了事,必須先進入下方「已中招/疑似中招」的應急處置流程。

🕳️ ㊃ 後門檢查點:確認有沒有人已經留了鑰匙在你家

決策原理:若前一步驟發現異常日誌,就得假設攻擊者已成功把 LFI 升級為 RCE,並在主機上留下後門維持長期存取權限。常見藏匿點包括 WordPress 特有的 Must-Use Plugins(mu-plugins,這類外掛在後台介面無法被停用,隱蔽性極高),或是近期被異常修改的 .php 檔案,也需一併確認是否出現不明的高權限管理員帳號 🛸

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

echo "① 掃描近 7 天內被修改的 PHP 檔案..."
find "$WP_PATH" -type f -name "*.php" -mtime -7 -print0 |
    xargs -0 -r ls -la --

echo "② 掃描 mu-plugins(隱形外掛)資料夾..."
MU_PLUGINS_DIR="$WP_PATH/wp-content/mu-plugins"
if [ -d "$MU_PLUGINS_DIR" ]; then
    find "$MU_PLUGINS_DIR" -type f -name "*.php" -print0 |
        xargs -0 -r ls -la --
else
    echo "沒有找到 mu-plugins 資料夾,這是正常狀態。"
fi

echo "③ 檢查目前所有管理員帳號..."
sudo -u "$OWNER" -- wp --path="$WP_PATH" user list \
    --role=administrator \
    --fields=ID,user_login,user_email,user_registered \
    --format=table

# ❇️ 獨立多站(逐站檢查管理員帳號)
SEARCH_ROOT="/var/www"

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

    printf '\n===== 檢查站點管理員:%s =====\n' "$site_path"

    sudo -u "$owner" -- wp --path="$site_path" user list \
        --role=administrator \
        --fields=user_login,user_email \
        --format=table
done

預期輸出範例:

ID user_login user_email user_registered
1 main_admin admin@example.com 2020-01-01 10:00:00
(若出現類似 hacker123 或近期才註冊的陌生信箱,即為紅旗警訊)

分支判斷邏輯:

  • ⭐ 發現來路不明的 PHP 檔案、未知的 mu-plugins 或陌生管理員帳號:代表網站已經完全淪陷,請立即尋求專業資安鑑識協助,或直接進入下方「已中招」應急處置。
  • ⭐ 以上檢查皆無異常:恭喜,攻擊者可能僅止於探測階段,可以繼續進行加固與正式修補。

🔧 ㊄ 加固檢查:把第二道門也順手鎖起來

決策原理:就算這次的 Divi Ajax Filter 漏洞補好了,未來依然可能出現其他外掛的 LFI 漏洞。系統層級的縱深防禦才是長治久安之道——設定 PHP 的 open_basedir 能嚴格限制 PHP 只能讀取網站根目錄的資料夾;disable_functions 則能阻斷大部分 LFI 升級到 RCE 所需的系統指令呼叫 📢

echo "檢查 PHP 安全設定..."
grep -Ei "^(open_basedir|disable_functions|allow_url_include)" /etc/php/*/fpm/php.ini

WP_PATH="/var/www/html"
echo "檢查 wp-config.php 檔案權限..."
stat -c "%A %a %n" -- "$WP_PATH/wp-config.php"

預期輸出範例:

設定項目 / 檔案 預期安全狀態
allow_url_include Off
disable_functions exec,passthru,shell_exec,system,proc_open,popen…
open_basedir 限制於網站目錄內,例如 /var/www/html:/tmp
wp-config.php 權限 -rw-r—– 640

分支判斷邏輯:

  • ⭐ 若發現 allow_url_include = On:極度危險,必須立刻改為 Off 並重啟 PHP-FPM。
  • ⭐ 若未設定 open_basedir:建議設定為網站根目錄加暫存資料夾,防止跨目錄存取系統檔案。
  • ⭐ 若 wp-config.php 權限過於寬鬆(如 777 或 644):應立即執行權限收斂,並確認擁有者設定正確。

💡 碎碎念:設定了 open_basedir 就百毒不侵了嗎?
並不是萬靈丹。過去曾有駭客利用 PHP 某些擴充模組底層的執行緒漏洞繞過這道限制。持續更新 WordPress 核心與外掛,把漏洞從應用程式源頭補起來,永遠才是最根本的解法 🎓

[34]


🟥 🟥 🟥

🛠️ 三條路:已中招、暫時卡住、正式修補

根據上面排查的結果,判斷你的網站目前處於哪個狀態,選擇對應的方案往下走。

🚨 A. 已中招/疑似中招:先止血,別急著清理

決策原理:一旦確認遭駭,首要任務是「保存證據」與「阻斷對外連線」,而不是急著刪除可疑檔案或按下更新按鈕。隨意變動檔案結構會破壞鑑識跡證,甚至可能觸發攻擊者預留的自毀機制 🛅

# 1. 緊急隔離(暫停 Web 服務,保留現場證據)
sudo systemctl stop nginx
# 若使用 Apache,請改成:sudo systemctl stop apache2

WP_PATH="/var/www/html"
EVIDENCE_DIR="/root/incident-evidence-$(date +%Y%m%d_%H%M%S)"
mkdir -p -- "$EVIDENCE_DIR"

# 2. 緊急取證打包
tar -czf "$EVIDENCE_DIR/access-logs.tar.gz" -- /var/log/nginx/ /var/log/apache2/ 2>/dev/null

tar -czf "$EVIDENCE_DIR/mu-plugins-and-uploads.tar.gz" \
    -C "$WP_PATH/wp-content" \
    -- mu-plugins uploads

# 3. 強制刷新登入鹽值,逼所有人重新登入
OWNER="$(stat -c '%U' -- "$WP_PATH/wp-config.php")"
sudo -u "$OWNER" -- wp --path="$WP_PATH" config shuffle-salts

分支判斷邏輯:保存證據並斷網後,若團隊具備清理惡意檔案的能力,可嘗試深度清理,清理完畢再進入正式修補;若缺乏專職資安團隊,最保險的做法是徹底砍掉重建——保留資料庫(但先盤點可疑管理員帳號),捨棄舊有網站目錄,用全新乾淨的 WordPress 核心、佈景主題與最新版外掛重新架設 🦺

🧯 B. 短期應急:暫時無法更新,但想先把門擋住

決策原理:某些企業環境因為客製化開發或需等主管簽核,導致無法立刻更新。這時可以在伺服器層級精準擋下帶有漏洞觸發特徵的請求,作為臨時緩衝 🛡️

location ~* /wp-admin/admin-ajax\.php {
    if ($arg_loop_templates = "custom-template") {
        return 403;
    }
    if ($arg_custom_loop_template ~* "(\.\.|%2e%2e|etc/passwd|%2fetc)") {
        return 403;
    }

    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php8.1-fpm.sock;
}

🚨 這只是暫時的緩衝措施,不是正式修補
如果這個攔截規則不小心影響了網站前台正常的篩選功能,為了維持業務運作,必須移除規則並強迫進入方案 C(正式修補)——資安防護的核心,不能拿相容性去交換 🛑

🚀 C. 正式修補:備份 👉 模擬 👉 更新 👉 驗證

步驟 1:備份——升級外掛可能引發相容性問題導致全站白畫面,完整備份是最後的救命繩。

# 💠 單一網站 & ✴️ Multisite(Network 的匯出動作,是匯出整個網路共用的資料庫)
WP_PATH="/var/www/html"
OWNER="$(stat -c '%U' -- "$WP_PATH/wp-config.php")"
BACKUP_DIR="$HOME/wp-security-backup"
STAMP="$(date +%Y%m%d_%H%M%S)"

mkdir -p -- "$BACKUP_DIR"

sudo -u "$OWNER" -- wp --path="$WP_PATH" db export \
    "$BACKUP_DIR/db_backup_${STAMP}.sql"

tar -czf "$BACKUP_DIR/files_backup_${STAMP}.tar.gz" -- "$WP_PATH"

# ❇️ 獨立多站(逐站備份,避免互相覆蓋)
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
    site_path="$(dirname -- "$config")"
    owner="$(stat -c '%U' -- "$config")"
    site_name="$(basename -- "$site_path")"

    printf '📦 正在備份站點:%s\n' "$site_name"

    sudo -u "$owner" -- wp --path="$site_path" db export \
        "$BACKUP_ROOT/${site_name}_db_${STAMP}.sql"

    tar -czf "$BACKUP_ROOT/${site_name}_files_${STAMP}.tar.gz" -- "$site_path"
done

🚨 獨立多站環境千萬別貪圖方便,直接用一條指令匯出全部資料庫
若不使用上面的迴圈逐站備份,而是圖方便一次匯出所有站台的資料庫,萬一之後只有其中一個站點升級失敗需要還原,這份龐大的全庫備份會導致伺服器上所有網站的資料庫被一起強制倒回舊時間點,引發無可挽回的營運災難 ⛔

步驟 2:Dry-run 模擬——利用 WP-CLI 的 –dry-run 參數,在不動任何檔案的前提下先確認升級包確實存在、連線正常。

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

sudo -u "$OWNER" -- wp --path="$WP_PATH" plugin update divi-ajax-filter --dry-run

步驟 3:正式更新——最佳實踐流程是「停用 👉 更新 👉 啟用」,避免覆寫檔案的瞬間剛好有大量前台請求湧入,導致解析到殘缺檔案而觸發錯誤。

# 💠 單一網站
WP_PATH="/var/www/html"
OWNER="$(stat -c '%U' -- "$WP_PATH/wp-config.php")"

sudo -u "$OWNER" -- wp --path="$WP_PATH" plugin deactivate divi-ajax-filter
sudo -u "$OWNER" -- wp --path="$WP_PATH" plugin update divi-ajax-filter
sudo -u "$OWNER" -- wp --path="$WP_PATH" plugin activate divi-ajax-filter

# ✴️ Multisite(外掛實體只需更新一次,啟用停用要加 --network)
WP_PATH="/var/www/network/public_html"
OWNER="$(stat -c '%U' -- "$WP_PATH/wp-config.php")"

sudo -u "$OWNER" -- wp --path="$WP_PATH" plugin deactivate divi-ajax-filter --network
sudo -u "$OWNER" -- wp --path="$WP_PATH" plugin update divi-ajax-filter
sudo -u "$OWNER" -- wp --path="$WP_PATH" plugin activate divi-ajax-filter --network

# ❇️ 獨立多站:務必逐站完成「備份👉更新👉驗證」後再處理下一站
# 切勿寫成一個巨大迴圈一次跑完所有更新,否則無法掌控單一站點的更新災情

步驟 4:三層驗證——確認版本號、檔案完整性、以及前台實際存活狀態。

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

echo "① 版本驗證..."
sudo -u "$OWNER" -- wp --path="$WP_PATH" plugin get divi-ajax-filter --field=version

echo "② Checksum 完整性驗證..."
sudo -u "$OWNER" -- wp --path="$WP_PATH" plugin verify-checksums divi-ajax-filter
sudo -u "$OWNER" -- wp --path="$WP_PATH" core verify-checksums

echo "③ 網站存活驗證..."
curl -s -o /dev/null -w "%{http_code}\n" -- "https://您的網站網址.com/"
驗證項目 預期輸出 意義
版本 5.1.3 已達官方安全版本
Plugin Checksum Success: Verified 1 of 1 plugins. 外掛檔案未被竄改
Core Checksum Success: WordPress installation verifies against checksums. WordPress 核心未被植入後門
HTTP 狀態碼 200 網站前台運作正常

分支判斷邏輯:若版本仍低於 5.1.3,代表更新流程失敗,需手動下載官方壓縮檔覆蓋;若 Checksum 報錯(例如提示某個檔案被修改),表示外掛可能已被植入惡意程式碼,需立即刪除後重新從官方乾淨來源安裝;若 curl 拿到 500 錯誤,代表更新造成網站崩潰,請立即回滾備份並另行排查 🛟

🫂 新手求助:不熟悉 SSH 或 WP-CLI 也沒關係
如果上面這些指令對你來說太陌生,可以直接參考 WP-CLI 官方文件的 plugin verify-checksumscore verify-checksums 指令說明頁面,或請你的主機商客服協助執行這一節的排查與更新流程 🆘

[35]


📋 📋 📋

🗂️ Runbook 總表:三種架構關鍵動作一次看

執行階段 💠 單一網站 ❇️ 獨立多站 ✴️ Multisite
1. 環境判斷 grep MULTISITE 無輸出 find 搜尋出多個獨立 wp-config.php wp core is-installed –network 回傳 1
2. 盤點比對 單次 wp plugin list find -print0 迴圈逐站掃描 單次 wp plugin list(實體共用)
3. 備份 db export + tar 打包 逐站獨立備份,避免互相覆蓋 db export 匯出整個網路資料庫
4. Dry-run –dry-run 模擬更新 逐站模擬更新 –dry-run 模擬更新
5. 正式更新 deactivate👉update👉activate 逐站完成備份👉更新👉驗證 –network 啟用停用,檔案只更新一次
6. 驗證 verify-checksums(核心+外掛) 逐站迴圈驗證+HTTP 狀態碼 驗證主站與各子站前台

🟠 🟠 🟠

🧠 舉一反三:同類型漏洞的通用防禦知識

這次事件屬於典型的 LFI 家族,底層原因都是「未經控制的外部參數,直接進了檔案解析流程」。以下幾個原則不是這個 CVE 官方要求的,但很值得研發與維運團隊放進日常加固清單:

  • 輸入白名單驗證:絕不能把未經驗證的外部參數直接傳進檔案載入函式。開發時應先建立允許載入的檔案清單,只有參數值確實存在於清單內才放行。
  • 路徑正規化與邊界檢查:若必須接受動態路徑,應利用 realpath() 展開路徑,搭配 basename() 剝除目錄穿越字元,再把解析出的絕對路徑與預期的安全基礎路徑做字串比對,確保沒有逃逸出邊界。
  • 最小權限原則:在作業系統層面妥善設定 PHP-FPM 的 open_basedir,並確保 Web 伺服器的執行身分沒有權限讀取系統密碼檔、SSH 金鑰等敏感區域,從底層截斷 LFI 發揮作用的空間。

🏁 🏁 🏁

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

評估維度 分數 一句話評語
漏洞嚴重程度(越高風險越大) 9.8 不用登入、零互動就能觸發,且攻擊流量容易藏在正常請求裡
官方應變速度 8 公開後短時間內即釋出修復版本,反應算快
一般站長自救可行性 9 點更新按鈕就能解決,操作門檻不高但急迫度極高
DevOps 技術文件完整度 7 攻擊參數與 CVSS 向量公開清楚,但官方尚未釋出詳盡的原始碼修補差異,仍有部分細節待確認
🏆 加權綜合建議 立即行動 最終建議:今天內完成更新,不要拖到明天

🔥 這起事件再次提醒我們一件事:漏洞不見得都躲在你天天在用的核心功能裡,有時候反而藏在一個「篩選功能」這種看似無害的小外掛背後。與其等哪天真的出事才手忙腳亂,不如現在花五分鐘,打開後台看一眼版本號碼 🔢

[36]


📎 📎 📎

📚 參考資料與延伸資源

列印本文 [43] 👨‍👩‍👧‍👦 24 次瀏覽