WordPress 惡意檔案清除 + 核心重裝完整 SOP:從停 PHP-FPM 到 verify-checksums

這篇需要 SSH 操作

這篇文章的操作需要透過 SSH 連進伺服器執行。還不熟悉 SSH?請先看SSH 連線教學。

掃描結果出來了。該清了。

分兩階段:先把攻擊者塞進來的惡意檔案清掉,再用乾淨的 WordPress 核心覆蓋被竄改的系統檔。

有些惡意程式會在 PHP 執行時即時把自己寫回來。你刪了,下一個請求進來它又出現。所以第一步不是刪檔案,是斷掉它的執行環境。

路徑說明

以下指令裡的 你的網站根目錄 請替換成你實際的網站路徑。不同主機商路徑不同:

RunCloud → /home/runcloud/webapps/網站名稱

Cloudways → /home/master/applications/xxx/public_html

cPanel → /home/帳號/public_html

不確定的話,跑 find / -name wp-config.php 2>/dev/null 搜尋。


先停 PHP-FPM

惡意檔案會自動重生的話,先停掉這個網站的 PHP-FPM pool:

kill $(pgrep -f "pool SITENAME")

停 FPM 之後會怎樣

這個網站前後台都會 502,完全無法訪問。

同一台伺服器的其他站不受影響,只有你指定的 pool 會停。

清完重啟就恢復了。


清惡意檔案

被植入的 index.php

掃描階段看到 goto 混淆、eval 那些的話,先解除檔案保護再刪:

# 解除 ACL 和 immutable 保護
setfacl -b 你的網站根目錄/index.php
chattr -i 你的網站根目錄/index.php
chmod 644 你的網站根目錄/index.php

# 刪掉被污染的 index.php
rm -f 你的網站根目錄/index.php

不用手動寫回乾淨版。等下核心重裝會覆蓋回來。

隨機命名的暫存 PHP

被植入的 index.php 通常會用 uniqid() 在根目錄動態產生隨機命名的 PHP 暫存檔。我們實際遇過的是 6a 開頭的,但你遇到的可能是其他開頭。先用 ls 看一下根目錄有哪些不認識的 PHP:

ls 你的網站根目錄/*.php

WordPress 根目錄下合法的 PHP 就那幾個:index.php、wp-*.php、xmlrpc.php。其他的(隨機字串命名、不在 WordPress 官方檔案清單裡的)幾乎都是惡意的。不確定的話,把清單貼給 AI 幫你判斷。

重複目錄裡的 cache.php

攻擊者在合法目錄裡建同名子目錄,塞一個 cache.php。像 Handler/Handler/cache.php,外層是合法的,裡面那個重複的是攻擊者建的。很多檔名掃描工具抓不到這種結構。

find 你的網站根目錄 -name "cache.php" | while read f; do
  dir=$(dirname "$f")
  parent=$(basename "$dir")
  grandparent=$(basename "$(dirname "$dir")")
  if [ "$parent" = "$grandparent" ]; then
    echo "刪除: $dir"
    rm -rf "$dir"
  fi
done

怎麼判斷哪些該刪

每次中毒遇到的惡意檔案名稱都不一樣,不能靠「背檔名」來判斷。以下是我們實際處理過的案例,列出來讓你理解攻擊者會塞什麼類型的東西:

  • 根目錄下不屬於 WordPress 的 PHP:我們遇過 hiroshi.php、item.php、wp-log1n.php,你遇到的檔名一定不同,但特徵一樣:不在 WordPress 官方檔案清單裡。
  • 隨機字串命名的目錄:像 eb7ec1/、0ef4b1/ 這種六七個字的 hex 目錄名,正常的 WordPress 不會有。
  • 偽造的核心子目錄:塞在 wp-includes/ 裡面但不屬於 WordPress 核心的目錄。我們遇過 abilities-api/、php-ai-client/,核心重裝後用 verify-checksums 跑出來的 “File should not exist” 清單就是它們。
  • 同一個檔名散佈多處:攻擊者喜歡把同一個後門丟到好幾個子目錄。用 find . -name "可疑檔名" 全站搜一遍,確認有沒有其他副本。

不確定某個檔案該不該刪?用 cat 檔案路徑 打開看內容,把前幾行貼給 ChatGPT 或 Claude,問「這個 PHP 檔案是惡意的嗎?」AI 能幫你判斷。

外掛目錄裡的可疑檔案

掃描命中合法外掛目錄裡的檔案,要用 cat 看一眼內容。以下幾種是明確後門:

  • eval(base64_decode($_COOKIE[...])) → cookie 後門
  • session_start + 密碼驗證 + eval → 帶密碼的 webshell
  • curl 下載外部檔案 + eval → 遠端載入後門
  • 檔名是隨機 hash:util-9e10e35c.php、helper-2e0adbeb.php

核心重裝

惡意檔案清完,用乾淨核心覆蓋被竄改的系統檔。

查版本:

wp core version --allow-root

下載乾淨核心覆蓋:

wp core download --version=版本號 --locale=zh_TW --force --allow-root

--force 覆蓋 wp-admin/ 和 wp-includes/,不會動 wp-content/。主題、外掛、上傳檔案都不受影響。

驗證核心完整性:

wp core verify-checksums --locale=zh_TW --allow-root

跑出 File should not exist 代表攻擊者在核心目錄塞了額外檔案,--force 不會刪它們,要手動處理。跑出 File doesn’t exist 代表有核心檔案被誤刪了,再跑一次 wp core download --force 補回來。

修權限(這步很容易忘)

用 root 跑 WP-CLI 的話,下載的檔案 owner 會變成 root。PHP-FPM 跑的是你主機商指定的使用者,權限不對直接 500:

chown -R 網站使用者:網站使用者 你的網站根目錄/

「網站使用者」是誰?看你的主機商:RunCloud 是 runcloud、Cloudways 是 master、cPanel 通常是你的帳號名稱。不確定的話跑 ls -la 你的網站根目錄/wp-config.php 看原本的 owner 是誰。

重裝完重啟 PHP-FPM 後網站報 500,十之八九就是忘了這步。


幾個提醒

–force 不等於全部重來。很多人以為跑了 wp core download --force 整個站就洗乾淨了。它只覆蓋核心檔案,攻擊者額外塞的東西它不管。verify-checksums 跑出來的 “File should not exist” 清單就是你要手動處理的。

清完之後,回到掃描那篇的指令全部重跑一次,跟第一次比對,確認沒漏掉。還有命中的話,用 cat 看是正常核心檔案還是漏網之魚。

檔案層清完不代表結束。資料庫裡的假帳號、應用程式密碼、惡意排程還在。後面的文章會講怎麼處理。

貼給你的 AI 用

掃描做完了,要開始清除?把下面這段貼給 ChatGPT 或 Claude,他會根據你的掃描結果判斷哪些該刪、哪些是正常的:

我的 WordPress 被駭了,掃描已經做完,現在要清除惡意檔案並重裝核心。我已經 SSH 進伺服器。

我的環境:
- 主機商:[填入,例如 RunCloud / Cloudways / cPanel]
- 網站路徑:[填入]
- WordPress 版本:[填入]
- PHP-FPM pool 名稱:[填入,不確定就寫「不確定」]

以下是我的掃描結果:
[把掃描結果貼在這裡]

請幫我:
1. 判斷掃描結果裡哪些是惡意的、哪些是正常的(逐一說明)
2. 根據我的主機商告訴我正確的網站路徑和使用者名稱
3. 引導我按順序清除,每一步等我回報再繼續:
   - 判斷是否需要先停 PHP-FPM
   - 清除被植入的檔案
   - 用 wp core download --force 重裝核心
   - 跑 verify-checksums,幫我判斷結果裡哪些要手動刪
   - 修正檔案權限(chown)

SSH 操作注意事項

SSH 指令送出去就是送出去了,沒有 Ctrl+Z。操作前請注意:

  • AI 給的指令,跑之前自己看一遍,確認路徑和檔名是對的再按 Enter。
  • 不要整段複製貼上一口氣跑完。一次一個指令,看到結果再下一步。
  • 刪除和覆蓋操作不可逆。備份沒做完之前,不要動任何清除步驟。
  • 不確定的時候就停下來問。問 AI、問主機商、問會的朋友,都比直接跑好。

SSH 基礎教學請看這篇。

搞不定?讓專人處理

WP 中毒救援 $5,500 起 | 外掛衝突排查含版面復原 $4,000 起

了解服務方案 →

文章分享