🔑 關鍵洞察
✦ AI·GEN這篇記錄了作者在一次例行效能健檢中,意外撞見自己家用伺服器被植入挖礦程式的完整抓賊過程,並整理成一份人人可照做的可疑進程自檢清單。起點是一個不該出現的數字:一台 16 執行緒的機器 load average 卻卡在 10.65。作者用 top 與 docker stats 揪出一個吃滿 10 核、隨機大小寫檔名的可疑程序,再靠 Linux 的 /proc 三件套(exe 指向已刪除的 /tmp 檔、cmdline、environ 溯源到某個 Docker 容器)確認它是挖礦程式,並把攻擊鏈拉出來:一個 port 直接綁在 0.0.0.0、繞過 nginx 對外裸露的 Next.js 容器,被 CVE-2025-66478(Next.js 16.0.6 的滿分 RCE、綽號 React2Shell)打穿。文中含清除(kill 不需 sudo、殺父程序回收 zombie)、查持久化(cron/bashrc/tmp)、堵根因(收回 127.0.0.1、升級框架、容器資源上限)的每一步命令,附一份六條的自檢清單。作者的體悟是:被入侵最可怕的不是被吃 CPU,是你根本不知道自己被入侵了。
那天我只是想順手幫自己的家用伺服器做個健檢,結果第一個指標就不對勁:。這是一台 16 執行緒的機器,平常閒著沒事,load 卻卡在 10.65,等於三分之二的 CPU 被某個東西持續吃著。
有東西在偷用我的機器。這篇是我把它揪出來、清掉、堵住入口的全程,而且我盡量寫成一份你可以照著跑一遍的檢查清單:怎麼確認自己的機器上有沒有這種偷跑的可疑程序。
第一步:誰在吃我的 CPU#
load 高就先看是誰佔的。從 uptime 一路追到揪出元兇,當時的終端記錄是這樣(數字都是那天實際跑出來的):
$ uptime
03:14 up 12 days, load average: 10.65, 9.90, 7.40
$ top -bn1 -o %CPU | head
PID USER %CPU %MEM COMMAND
2714011 alice 996.0 3.9 XXAkjjBB
525303 alice 429.0 0.0 XXigEEFC
$ docker stats --no-stream
NAME CPU % MEM USAGE
nas-frontend 1017.32% 2.41GiB三個數字沒一個對:一台 16 執行緒的機器閒著,load 卻是 10.65;一個叫 XXAkjjBB、隨機大小寫檔名的東西吃 996% CPU(佔滿 10 個核心)、2.4GB 記憶體;連跑 Next.js 的前端容器 nas-frontend 都顯示 1017% CPU。
那個 XXigEEFC 是個 ,CPU 欄卻是 429,一個已經死掉的程序不該有 CPU 數字,這是計數殘留。
一個正常的服務不會叫這種名字。隨機大小寫、無意義的檔名,是挖礦程式最典型的長相,它就是要讓你在 process 列表裡一眼認不出來。
第二步:用 /proc 三件套鑑定它#
看到可疑 PID 先別急著殺,先問它三個問題:你是什麼、你怎麼被啟動的、你從哪來。Linux 的 /proc/<pid>/ 把答案都攤在那裡,不需要任何額外工具。當時我跑的三條,和它們吐回來的東西:
$ ls -la /proc/2714011/exe
lrwxrwxrwx 1 alice alice 0 ... /proc/2714011/exe -> '/tmp/XXAkjjBB (deleted)'
$ cat /proc/2714011/cmdline | tr '\0' ' '
/tmp/XXAkjjBB
$ cat /proc/2714011/environ | tr '\0' '\n'
NODE_VERSION=20.11.0
HOSTNAME=0.0.0.0
PORT=3001
...三條各供出一塊拼圖:
exe指向 :執行檔放在/tmp,而且已經從磁碟刪掉了,程序卻還在跑。合法服務不會這樣,這是惡意程式「落地即自刪」的標準動作。environ那串NODE_VERSION、HOSTNAME=0.0.0.0、PORT=3001,不像我登入 shell 會有的變數,比較像某個 Node.js Docker 容器內部的環境。這隻挖礦程式,是從我其中一個容器裡被生出來的。
第三步:把攻擊鏈拉出來#
順著父程序往上追(ps -o ppid= -p <pid>,或 htop 開 tree view),整條鏈就清楚了:
父程序是 next-server,也就是我一個跑 Next.js 的前端容器。它被入侵後,依序 spawn 了 sh(拿到 shell)、base64(解碼夾帶的 payload),最後跑起 XXAkjjBB 挖礦。這就是為什麼那個容器在 docker stats 裡顯示 1017% CPU:吃 CPU 的其實是它 fork 出來的挖礦程式。
第四步:清除#
好消息是,這隻挖礦程式是用我的普通使用者身分(不是 root)在跑的:它從一個以我身分啟動的容器裡被生出來,繼承了我的權限。所以殺它不需要 sudo:
# 殺挖礦程序(用你自己的身分就能殺掉你身分跑的程序)
kill -9 2714011
# zombie 殺不掉,因為它已經死了;要回收它得殺它的父程序
docker stop <被入侵的容器>殺完盯著 load average 看它往下掉:10.65 → 8.80 → 6.30,CPU 開始退燒。zombie 在父容器停掉後才會被 init 收走、真正消失。直接 kill 一個 zombie 沒有用,它已經死了,你要處理的是還活著的父程序。
第五步:確認它沒有留後門#
殺掉當下的程序只是止血。真正要命的是持久化:如果攻擊者在某個開機腳本或排程裡埋了「重新下載並執行」,你殺完它下一分鐘又活過來。挖礦程式最愛藏的三個地方,逐一檢查:
# 1. 排程:cron 是最常見的重生管道
crontab -l
cat /etc/crontab
ls -la /etc/cron.d/ /etc/cron.*/
# 2. shell 啟動檔:登入時自動執行
cat ~/.bashrc ~/.profile ~/.bash_profile 2>/dev/null | grep -iE 'curl|wget|base64|/tmp|http'
# 3. /tmp 裡還有沒有其他可執行檔
find /tmp -type f -executable -ls 2>/dev/nullWARNING
如果你在這幾個地方發現了可疑的東西,問題比單一程序嚴重得多
一個掛在 cron 裡每分鐘 curl … | bash 的項目,代表攻擊者已經取得能寫入你排程的權限,單純殺程序完全不夠。我這次三處都乾淨(/tmp 清空、cron 沒被動、shell 啟動檔正常),所以確定它還沒站穩腳跟,止血就足夠。但這一步不能跳過:不查持久化就宣告清除完成,是資安事件處理最常見的錯誤。
根因:一扇沒關的門,和一個過期的鎖#
止血之後,真正的問題是:它怎麼進來的?兩個因素疊在一起,缺一不可。
第一,那個前端容器的 port 直接綁在 0.0.0.0。 它本該只透過 nginx 反向代理對外,結果 docker-compose 裡寫的是 13001:3001,而不是 127.0.0.1:13001:3001。這個差別是致命的:
。前者讓任何人都能直接打「我的公網 IP:13001」,完全繞過 nginx 和 Cloudflare 那層防護。我查 nginx 的 access log 時發現裡面根本沒有這隻容器的紀錄,就是因為攻擊流量從來沒經過 nginx,直接敲到裸露的 container port。
第二,那個容器跑的 Next.js 是 16.0.6,一個有已知 漏洞的版本。 門開著(port 裸露)加上鎖是壞的(過期的框架),攻擊者用一個公開的漏洞就取得了 shell,剩下的就是下載挖礦程式的例行公事。
這個 RCE 有名有姓:CVE-2025-66478(上游是 React Server Components 的 CVE-2025-55182,綽號 React2Shell),CVSS 滿分 10.0。Next.js 16.0.0 到 16.0.6 都中招,攻擊者送一個帶特製 Next-Action header 的請求、不需要任何帳密就能執行任意程式碼,16.0.7 才修掉。而它 2025 年 12 月就公開了,我卻是 2026 年 4 月才發現自己中招。中間那幾個月,就是我一直沒升級、門開著鎖也壞著、放給人打的空窗期。
加固:把整台機器的門都檢查一遍#
補完這個洞,我做的第一件事是假設「如果這扇門開著,會不會還有別的門也開著」。掃了整台機器的 port 綁定:
# 列出所有對外(0.0.0.0)監聽的 port
ss -tlnp | grep '0.0.0.0'
# Docker 的話也看一下每個容器的映射
docker ps --format '{{.Names}}\t{{.Ports}}'結果揪出一整排本該只走反代、卻裸奔在 0.0.0.0 的服務。最讓我背脊發涼的一個,是一個 PostgreSQL 資料庫直接對公網開著,那比挖礦嚴重多了,是整個資料庫隨時可能被撈走。全部收回 127.0.0.1,只留 nginx 進得去。
三件事收尾:
任何「只應該透過 nginx 存取」的容器,port 都加上 127.0.0.1: 前綴。對外的入口只留 nginx 一個,其餘服務對公網一律隱形。這是這次事件成本最低、效益最高的一條。
把有 RCE 的 Next.js 從 16.0.6 升到當時的最新穩定版。門要關(綁 127.0.0.1),鎖也要換新(修掉已知漏洞),兩件都做,才不會下次換個漏洞又被同一扇門打進來。
替所有容器加 mem_limit 和 cpus。這不防入侵,但限制爆炸半徑:萬一再有一個容器被植入挖礦,它最多吃到自己那份配額,不會像這次一樣一隻程序吃滿 10 核、把整台機器拖垮。
一份你可以現在就跑的自檢清單#
如果你也管著一台對外的機器(VPS、家用伺服器、甚至只是開了 port forwarding 的家用電腦),花五分鐘照這份跑一遍:
可疑進程自檢清單(逐條照跑)
- 看 load 與 CPU:
uptime看 load average,top按 CPU 排序。閒置機器 load 卻很高,或有你不認得的程序吃滿 CPU,就是紅旗。 - 可疑檔名:
ps aux --sort=-%cpu | head,隨機大小寫、無意義的 COMMAND 名,高度可疑。 - 鑑定它的執行檔:
ls -la /proc/<pid>/exe。指向/tmp、/dev/shm,或帶(deleted)標記的,幾乎確定是惡意的。 - 看它怎麼啟動、從哪來:
cat /proc/<pid>/cmdline | tr '\0' ' '和cat /proc/<pid>/environ | tr '\0' '\n',環境變數會告訴你它是從哪個容器/服務被生出來的。 - 查持久化:
crontab -l、cat /etc/crontab、ls /etc/cron.d/,以及~/.bashrc裡有沒有curl … | bash之類的東西。 - 查對外的門:
ss -tlnp | grep 0.0.0.0列出所有對公網監聽的 port,逐一問自己「這個真的需要對外嗎」。資料庫、管理面板、內部 API 幾乎都不需要。
這次的帳算下來:一隻吃滿 10 核的挖礦程式、一個對公網裸奔的資料庫、一個過期到有 RCE 的框架,全都在同一次健檢裡被翻出來。修起來不難,難的是知道要去看。
- Next.js CVE-2025-66478 —— 這次被打穿的 RCE 官方公告nextjs.org
- Linux /proc 檔案系統 —— 程序鑑識的第一手資料man proc
- Docker —— 只綁 localhost 的 port 映射語法Docker Docs
- OWASP —— 伺服器加固與最小暴露面原則OWASP
還沒有留言
✨ 成為第一個留言的人吧