INC-2026-0915critical挖礦劫持 · 偵測篇

一台沒人看的排程機, 替別人挖了 356 天的礦

從「沒事做卻很忙」的矛盾出發,五條線索逐步逼出 rootkit、雙幣礦工與後門帳號。全程唯讀,未更動主機任何檔案。

事件期間 2025-09-24 → 2026-09-15·整理於 2026-09-17·11 分鐘·7 可證明2 推論1 缺口
挖礦劫持RootkitLinux 鑑識AWS EC2
tl;dr
  1. 業務早已失效的語音 AI 排程機持續單核滿載,最耗 CPU 的行程名稱是假的。
  2. ss 抓到連往 OVH 機房 3333 埠的 ESTABLISHED 連線,發起程序叫 log,執行檔在 /var/log/log。
  3. 讀取可疑檔案就 segfault,證實 LD_PRELOAD rootkit;再找到三重自啟與免密碼 sudo 的後門帳號。
◉ 攻擊者視角一台對外開著免驗證 RCE、又沒人登入的機器,是最理想的礦工宿主:穩定、安靜、電費別人付。
◎ 防守者視角「沒事做卻很忙」是最早也最便宜的訊號。閒置主機的 CPU 告警加上出向流量管控,第一天就能抓到。
本篇目錄
:3333load 1.7 · cpu 100% · 業務已失效

先知道「正常」長什麼樣

在判斷「異常」之前,得先知道這台機器原本該做什麼。這台排程機唯一的業務,是一支處理員工錄音的流程:每 15 分鐘由 crontab 觸發,讀試算表拿到錄音清單、下載原始檔、用 Whisper 轉逐字稿、用 GPT 改寫、用 TTS 合成語音、再回寫試算表。

問題是,這條流程其實已經死了。它依賴的服務帳戶早在數月前被刪除,每次執行都在 log 尾端固定拋出 Invalid JWT Signature。業務面沒有任何產出,CPU 卻長期滿載。

第一個該追下去的矛盾

「沒事做卻很忙」。一台該閒置的機器持續高負載,不一定是挖礦,但一定值得追問「這個負載能對應到哪一項合理的工作?」合法報表也會高負載,所以單一指標不能定案,它只是調查的起點。

1.7
load average(單核 100%)
17,613 分鐘
假行程累積的 CPU 時間
:3333
對外連線目的埠
已失效
業務狀態

五條線索,逐步逼出真相

鑑識不是靠掃毒軟體給答案,而是每看到一個不合理,就追問「為什麼」。以下是實際的判讀順序與現場證據。

線索 1:CPU 滿載,但最耗資源的行程「說謊」

ps aux --sort=-%cpu | head -3READ-ONLY
USER   PID  %CPU %MEM     TIME  COMMAND
root   404  25.9  8.3 17613:25  /usr/bin/python3 .../unattended-upgrade-shutdown --wait-for-signal
mysql  562   0.3 18.2   218:45  /usr/sbin/mysqld

# 一支「關機前套用更新」的程式燒了 17,613 分鐘 CPU?不合理。

線索 2:一條連往境外機房 3333 埠的連線

換個角度:不看行程,看連線。

sudo ss -tnp | grep -v :22READ-ONLY
State  Local Address:Port   Peer Address:Port      Process
ESTAB  10.x.x.x:36708       141.94.96.71:3333      users:(("log",pid=479))

# 141.94.96.71 → OVH SAS / 法國。port 3333 = 礦池 Stratum 常用埠。
# 發起程序名叫 "log",執行檔卻是 /var/log/log。

線索 3:檔案一被讀取就 Segmentation fault

想算 /var/log/log雜湊去比對惡意程式庫,結果一碰就 core dump。連 /etc/ld.so.preload 都讀不了。

rootkit 讀取即崩潰READ-ONLY
$ sudo sha256sum /var/log/log
Segmentation fault      /var/log/log (core dumped)

$ sudo cat /etc/ld.so.preload
Segmentation fault      /etc/ld.so.preload (core dumped)

# 一般檔案讀取不會 segfault。這是 LD_PRELOAD rootkit 的防偵察反應,
# 也解釋了為何 ps 抓不到礦工:它被藏起來了。

線索 4:三重開機自啟

透過 /proc/479/exe(rootkit 攔不到的路徑)確認礦工本體就是 9.4 MB 的 /var/log/log。再往持久化機制找:

systemd + crontabREAD-ONLY
$ cat /etc/systemd/system/log.service
[Unit]
Description=log Plugin Service
[Service]
ExecStart=/usr/bin/log -sshd
Restart=always
User=root

$ sudo crontab -l
@reboot /var/log/log > /dev/null 2>&1 & disown

# 偽裝成「log 外掛服務」+ root 的 @reboot,加上 ld.so.preload,三重保險。

線索 5:一個免密碼 sudo 的「系統帳號」

/etc/passwd + sudoersREAD-ONLY
$ grep -vE 'nologin|false' /etc/passwd
root:x:0:0:root:/root:/bin/bash
ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/bin/bash
systemd:x:1001:1001::/home/systemd:/bin/bash      # ← 假的系統帳號

$ sudo grep -r '' /etc/sudoers*
systemd ALL=(ALL) NOPASSWD:ALL

$ sudo chage -l systemd | head -1
Last password change : Sep 23, 2025               # ← 入侵時間錨點

這次外洩了什麼

這台機器上沒有雲端憑證,所以「外洩」的東西跟一般想像的不一樣:被拿走的是登入能力本身,加上兩把寫死在腳本裡的 API key

leaked credentials這次外洩了什麼:服務、用途、存在形式
服務憑證類型用途存在形式放在哪權限範圍狀態
受害主機(Linux 本機帳號)攻擊者自建的可登入帳號與密碼攻擊者的長期登入管道,偽裝成系統帳號/etc/passwd 條目(有 home 與 shell)+ /etc/shadow 密碼雜湊受害主機本機搭配 sudoers 規則等同 root2026-09-15 起主機隔離、保持 stopped,待砍掉重建
受害主機(sudo 授權)免密碼 sudo 規則(等於免憑證提權)讓應用程式帳號與後門帳號直接取得 root明文 sudoers 檔案中的一行 NOPASSWD:ALL 規則受害主機 /etc/sudoers以 root 執行任意指令重建時一併移除
對外開放的開發用 API沒有憑證:端點本身就是入口原為內部測試用途,卻經反向代理對公網開放HTTP 請求參數被拼進 shell 指令字串受害主機的 Flask 應用以應用程式帳號執行指令,再經 sudo 到 root入侵前 log 已被清除,無法直證是由此進入
AI 服務供應商 API(語音轉文字與文字改寫)長期有效的 API key × 2排程流程呼叫轉逐字稿與改寫原始碼常數:明文字串寫死在排程腳本裡受害主機的腳本目錄(root 可讀)該供應商帳號的 API 用量與帳單必須視為已外洩並撤銷;是否被盜用需查供應商用量
同叢集主機的 SSH 金鑰/共用密碼可能被沿用的登入憑證維運人員登入其他主機格式符合 PEM 私鑰或共用密碼,但無法確認這台上是否留有可用的一份未取得完整盤點若共用即等於橫向擴散路徑gap:僅能假設有,處置時一併輪替
攻擊端:礦池收款識別(SupportXMR/C3Pool)門羅幣錢包位址+worker 代號把偷來的算力換成錢礦工設定 JSON 的 user/pass 欄,只常駐在行程記憶體攻擊者的 XMRig 設定與 Stratum 登入封包攻擊端 IOC,不是我方憑證已提交礦池;SupportXMR 回覆已將該位址 denylist
憑證本身已遮蔽或失效;這裡只說明它是哪個服務、拿來做什麼、以什麼形式存放,讓你能對照自己的環境。

時間軸

  1. 2025-09-23
    後門帳號 systemd 建立
    密碼設定日與惡意檔案時間戳一致。
  2. 2025-09-24 05:30
    植入 XMRig、rootkit sshdd.so、第二礦工
    離線磁碟分析取得檔案時間戳。
  3. 2025-09-24 00:00 之前
    入侵前的網站存取 log 全數消失
    access.log 最早一筆停在 9/24 00:00;攻擊者清過 log。
  4. 2025-09-24 → 2026-09-15
    潛伏約 356 天,單核持續替攻擊者挖礦
    業務失效後無人登入,所以無人發現。
  5. 2026-09-15
    偵測 → 活體取證 → 錢包 → 離線鑑識 → 關機封存
    全程唯讀。

356 天的帳

這台機器的業務早就死了,帳單一天也沒停。把雙方的帳擺在一起看:我們付的是雲端機時,他賺的是門羅幣

cost這台機器替別人挖礦的帳:用量、單價、期間成本
項目用量單價期間US$NT$舉證
EC2 t2.small(排程機)
估:業務早已失效,這 356 天的機時等於全部白付
8,544 小時(356 天 × 24)US$0.0304/小時2025-09-24 → 2026-09-15259.748,312期間可證明(後門帳號 chage -l 密碼設定日+離線磁碟檔案時間戳);金額為牌價 × 機時推算,未匯出 Cost Explorer 明細
EBS 根磁碟 gp2
估:文中未載明容量,以常見的 30 GB 根磁碟計
30 GB × 11.7 個月US$0.12/GB·月同上42.121,348容量為假設值,無帳單佐證;要證明需 Cost Explorer 的 EBS:VolumeUsage 明細
公網 IPv4 位址
估:2024-02 起 AWS 對所有公網 IPv4 位址計費
8,544 小時US$0.005/小時同上42.721,367主機有對外服務(nginx 對公網開放)可證明位址存在;金額為牌價推算
對外流量(提交 share)
估:挖礦幾乎不吃頻寬,所以帳單上看不出任何異常——這點比金額重要
約 2 GB/年每月首 100 GB 免費,超出 US$0.114/GB同上0.000無 VPC Flow Logs,流量僅由 Stratum 協定特性推估
我方偵測與判讀人力
估:偵測這件事本身,比這台機器一年的機時還貴
約 1 人日:五條線索判讀、活體取證、叢集盤點顧問日費 NT$20,000~35,000(估),取中間值 NT$27,5002026-09-15859.3827,500當日鑑識步驟推估,無正式工時單;日費為假設值
攻擊者的植入成本
估:他真正的成本只有掃描時間
1 次入侵,工具全是現成品公開的 XMRig 與 rootkit,取得成本 02025-09-240.000離線鑑識取得的 binary 為公開版本 XMRig 6.22.2,非自行開發
攻擊者的聚合 proxy 主機
估:以入門級 VPS 月租推估;同一台同時吃上百台肉雞的算力
1 台歐洲機房 VPS × 12 個月約 US$7/月356 天84.002,688proxy 位置可證明(ESTABLISHED 連線至該 IP,OVH SAS);租金為公開牌價推估
攻擊者從本機賺到的
估/推論:22.19 XMR 是整個 C4 活動的總額,不是本機損失
1~2 KH/s ÷ 148 KH/s × 22.19 XMR ≈ 0.15 XMR(取下限)≈ US$509/XMR(依下一篇 2026-09-15 匯率換算)356 天76.422,445礦池 API 的活動總額可證明,但單機算力佔比無法從礦池端取得,只能用機型算力推估
寫死在腳本裡的兩把 API key 被盜用的金額
必須視為已外洩並撤銷
缺口:未取得該供應商同期用量帳單,無法確認有沒有被消費
這台是否還被拿去做別的事(跳板、掃描、寄信)
只能假設有
缺口:入侵前的 nginx access.log 已被清除(最早一筆停在 2025-09-24 00:00),且無 VPC Flow Logs
合計(僅可證明列)0.000
匯率假設 1 USD ≈ NT$32。單價為公開牌價或帳單實付,來源:AWS 公開牌價 ap-northeast-1(2026-09 查詢);攻擊者那幾列是推估,列出只為對照。「舉證」欄標明每筆損失的證據來源與等級( 可證明 推論 缺口)。

可證明的金額:US$0。 這張表沒有一列是 proven,因為我們手上沒有這段期間的帳單明細——要把它升級成可證明,只要去 Cost Explorer 匯出這台在該期間的 BoxUsage 機時就行。這正是下一篇要講的:損害量化用機時,不是用礦池上的數字。

而推估的部分已經很清楚:他從這台賺到約 US$76,我們為此付出的雲端費用約 US$344(不含工時)。挖礦劫持的經濟學就長這樣:他的收益只是你成本的零頭,因為機時、磁碟、位址、電力、人力全部由你支付。

為什麼會被攻進來

主機上一支開發用的 Flask 對外開了一個端點,把使用者傳入的參數直接拼進 shell 執行,沒有任何驗證或白名單:

# 使用者可控的 param_n、param_data 直接進了指令字串
cmd = "python3 /var/www/app/" + param_n + ".py " + param_data
subprocess.run(cmd, shell=True)

整條路徑短得驚人:公網 → nginx :80 → Flask :5000 → 指令注入 → 以 ubuntu 執行 → ubuntu 有 NOPASSWD:ALL → root

發生了什麼 · 長期存在
開發用 API 經 nginx 反向代理整條對公網開放,沒有任何驗證。
當時留下的訊號
ss -tlnp 可見 5000 埠監聽;nginx access log 有陌生來源打 /api/ 路徑(本案已被清除)。
能擋下它的控制
開發/內部 API 一律綁 127.0.0.1 或內網 SG;對外只留反向代理後有驗證的路徑。

攻擊者 vs 防守者

他要的不是你的資料,是一顆穩定的 CPU。
  • 免驗證 RCE 加免密碼 sudo,從公網到 root 只要兩步,成本趨近於零。
  • 業務失效、沒人登入的機器,是最穩定的礦工宿主:沒人會看 top,也沒人會重開機。
  • 行程改名、rootkit 藏檔、三重自啟、假系統帳號,每一層都在拉長「被發現」的時間。潛伏 356 天,每一天都是收益。
  • worker 代號 C4 暗示這只是他上百台肉雞的其中一台。你不是被鎖定的目標,你是被掃到的。

已經中了怎麼辦

遇到 root 級 rootkit,不要嘗試就地清毒。你無法確認清乾淨,因為你看到的系統狀態本身就不可信。

  1. 隔離:改 Security Group 切斷對外連線(尤其 egress),但先別關機。關機會失去記憶體裡的跡證。
  2. 存證:趁礦工活著採集記憶體與封包(下一篇),再對 EBS 建立 snapshot
  3. 輪替金鑰:撤銷這台碰過的所有憑證:API key、SSH key、共用密碼。到對應後台檢查是否有異常用量。
  4. 盤點連動:同一把 SSH pem、同一組密碼是否被其他主機共用?一併輪替。移除 DNS 記錄與健康檢查。
  5. 銷毀重建:terminate 並確認 EBS 一併刪除、Elastic IP 釋放;從乾淨映像重建,切勿沿用舊 volume。

就算移除了已知的礦工與後門,也無法證明沒有殘留的第二層植入。唯一能回到已知乾淨狀態的方法,就是砍掉重建。

重點學習

keywords重點學習:最需要學習的 10 個關鍵字10
  1. 01
    load average(閒置卻高載)
    「沒事做卻很忙」是本案最早、最便宜、也是唯一真正有效的訊號,整條調查從這個數字開始。
  2. 02
    ss -tnp
    ps 會被 rootkit 騙,連線表不會;rootkit 在場時這是少數還能相信的介面。
  3. 03
    ld.so.preload / LD_PRELOAD
    檔案一讀就 segfault 的原因,也是「你看到的系統狀態本身不可信」的根源。
  4. 04
    /proc/PID/exe
    行程名稱可以造假,這條符號連結指向真正的執行檔,礦工本體就是靠它認出來的。
  5. 05
    Stratum 礦池埠(3333/5555/9443)
    主機主動外連這些埠,幾乎等同確診;也是最容易設告警的一條防線。
  6. 06
    持久化(systemd unit/@reboot/preload)
    惡意程式很少只有一個啟動點,只清掉找到的那一個,隔天它就回來了。
  7. 07
    NOPASSWD:ALL
    應用程式執行帳號有這一行,公網上的一個請求就能直接變成 root。
  8. 08
    shell=True 指令注入
    本案破口的形狀:使用者參數被拼進指令字串,「資料」就變成了「命令」。
  9. 09
    chage -l(密碼變更日)
    後門帳號的密碼設定日是最可靠的入侵時間錨點,尤其在 log 被清掉之後。
  10. 10
    egress 管控
    你未必擋得住他進來,但可以讓算力送不出去;礦工連不上礦池就沒有收益。

今天就可以來試試

try today今天就可以來試試15 分鐘

全部唯讀,不改任何設定;做完你會知道自己有沒有同樣的破口。

  1. 挑一台你說不清楚「現在在忙什麼」的主機,看負載與最耗 CPU 的行程。
    uptime
    ps aux --sort=-%cpu | head -5
    看到什麼代表什麼:load 接近核心數、但你講不出這個負載對應哪一項業務,就進入下一步;能對上合法工作就先放過。
  2. 看它主動連去哪裡,排除 SSH 與一般 HTTPS。
    sudo ss -tnp | grep ESTAB | grep -vE ':22|:443'
    看到什麼代表什麼:出現 3333、5555、7777、9443、13333、14444 這類埠,或連往你不認識的境外機房,直接當已感染處理,先別關機。
  3. 檢查有沒有 preload 型 rootkit 的痕跡。
    ls -l /etc/ld.so.preload 2>&1
    cat /etc/ld.so.preload 2>&1
    看到什麼代表什麼:檔案存在而你從沒裝過任何 preload 函式庫,或讀取時直接 segfault,代表 ps/ls/top 的輸出全部不可信。
  4. 盤點可以登入的帳號,以及誰有免密碼 sudo。
    grep -vE 'nologin|false' /etc/passwd
    sudo grep -rh NOPASSWD /etc/sudoers /etc/sudoers.d 2>/dev/null
    看到什麼代表什麼:有 home 目錄與 shell 的「系統風格」帳號幾乎必定是後門;應用程式帳號有 NOPASSWD:ALL,等於把提權送給對方。
  5. 盤點開機自啟點。
    ls -la /etc/systemd/system/*.service | tail -20
    sudo crontab -l
    看到什麼代表什麼:服務描述含糊(像是某某 Plugin Service)、或 root 的 @reboot 直接執行 /var、/tmp 下的檔案,都要追到底。
  6. 看這台實際對外開了哪些埠。
    sudo ss -tlnp
    看到什麼代表什麼:5000、8888、9200 這類開發/除錯埠綁在 0.0.0.0,就是對公網開著的門,與 Security Group 對照後排入本週處理。
§
對應課程模組

M4 主機偵測(12 條獵捕指令)· M5 活體取證與離線鑑識 · 想在自己的環境做一次同樣的盤點,從 自我盤點 開始。

newsletter
下一篇拆解直接寄到信箱

每篇新文章一封:三點摘要、攻擊者/防守者視角、今天能做的一件事。或先 加入會員 記錄你完成的任務。