如果让我给那次事故总结一个关键词,不会是“挖矿木马”,也不会是“Rootless”,而是“轻敌”。我始终以为自己跑了三年 Docker,隔离做得足够好,服务器安全最多就是改改 SSH 端口的事。直到一个周三下午,CPU 告警把我打醒:top 里一个叫 kdevtmpfsi 的进程占满了所有核心,Docker 容器列表里多出了几个“自己从未创建过”的容器,其中一个还挂载了宿主的整个根目录。那一刻我才明白,这台机器早就不属于我一个人了。
这篇复盘会完整还原从挖矿木马入侵、排查清理,到最终迁移到 Docker Rootless 加固的整个过程。我会重点讲清楚攻击者是怎么借 Docker 管理口打进宿主的、为什么普通清理只是“治标不治本”,以及 Rootless 模式从原理到落地时踩过哪些坑。如果你也用 Docker 跑过线上服务,尤其是曾把 Docker API 暴露到公网,那这篇内容应该能帮你省掉一次和我一样的血泪教训。
1. 事发当天:从一条 CPU 告警开始的安全复盘
1.1 我看到的第一个异常进程
那天下午的监控告警其实很普通:服务器 CPU 使用率持续 98% 以上。我登录服务器后第一件事就是跑 top -c,结果看到一串 kdevtmpfsi 进程,每个进程都占了接近 100% 的 CPU,加起来把整台机器的核全吃满了。说实话,我当时没立刻意识到那是挖矿木马,还以为是某个 Java 应用出了死循环。
顺着进程 PID 查 /proc 下的可执行文件路径,发现进程启动目录都在 /tmp 和 /var/tmp 下面,这才觉得不对劲。正常的业务进程不会从 /tmp 启动,更不会起十几份互相“看着像兄弟”的进程。再配合系统的 load average,我可以确认这绝不是普通的性能问题,而是有人往机器上种了挖矿木马。
随后我检查了 crontab -l,里面出现了一条“从远程地址下载 shell 脚本并执行”的定时任务,大概每小时执行一次。这就解释了为什么木马进程被手动 kill 之后,过一段时间又会重新出现——它不是靠自己复活,而是靠定时任务拉起来,而且我怀疑不止一个定时任务入口。
1.2 Docker 容器列表里的“不速之客”
因为这台服务器主要跑 Docker,我的下一个动作是 docker ps -a。不看不要紧,一看冷汗就下来了:列表里有几个名字完全是随机字符串的容器,创建时间恰好是 CPU 告警之前一两个小时。更刺眼的是,我用 docker inspect 查看其中一个容器的挂载信息时,看到了这样的输出:
code复制docker inspect -f '{{.Name}} | {{.Mounts}}' <异常容器ID>
/evil_container | [{ / /host rw }]
简单说,这个容器把宿主的根目录 / 挂载到了容器内部的 /host,并且是可读写权限。容器里的命令行是运行一段 shell 脚本,脚本内容大致是往宿主机 /etc/cron.d/ 下写入定时任务、往 /root/.ssh/authorized_keys 里追加公钥,再下载挖矿程序。
那一刻我的第一反应是“容器逃逸了”。但静下来细看,并不是传统意义的逃逸,而是有人通过 Docker API 直接控制 daemon,发起了正常但恶意的容器创建指令。攻击者根本不需要逃逸,因为 Docker daemon 本身就以 root 身份运行,谁能控制 Docker API,谁就等价于拿到了宿主机的 root shell。
1.3 第一次排查时我犯了哪些错
事情发生时我的处置顺序并不理想。我先做的是直接 docker rm -f 删掉异常容器,然后 kill 掉 kdevtmpfsi 进程。结果大约 5 分钟后,监控又出现 CPU 飙升。原因很简单:我只处理了“前台”的恶意容器,却漏掉了攻击者已经写入宿主机的 cron 任务和 SSH 公钥。定时任务每几分钟就会重新拉一个新容器,或者直接下载新的木马二进制到 /tmp 执行。
第二次我决定不急着杀进程,先做证据留存。把 docker ps -a 的输出、进程列表、crontab 内容、可疑文件哈希统统保存下来。因为挖矿木马圈子经常互相“踢馆”,攻击者之间会互相删对方的文件、占资源,这种混乱现场如果不先留存,事后很难回溯完整攻击路径。
这轮排查给我最深刻的教训是:面对安全事件,动作要快,但顺序不能乱。先隔离入口、再清理持久化项、最后杀进程,顺序反了就会掉进“杀完又复活”的循环。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 攻击路径拆解:挖矿木马是怎么进来的
2.1 暴露在公网的 Docker API 是罪魁祸首
清理完木马后,我翻开 Docker daemon 的配置,找到了真正的根因。早前为了图方便,我在 /etc/docker/daemon.json 里加了一行配置:
code复制{
"hosts": ["tcp://0.0.0.0:2375", "unix:///var/run/docker.sock"]
}
这行配置的本意是让我可以从办公网络远程调用 Docker 接口,方便部署容器。但我犯了一个致命错误:2375 端口是 Docker 的明文 API 端口,默认没有任何认证。也就是说,只要这台服务器有公网 IP,并且防火墙没挡住 2375,全世界任何人都可以直接向这个端口发送 Docker 操作指令。
攻击者不需要密码,不需要公私钥,只需要一条 HTTP 请求就能创建容器。扫描器在公网上会持续扫描这类未授权的 Docker API,发现后基本是秒级响应,会用自动化脚本立刻部署挖矿容器。从我的服务器日志看
