一次 Docker 容器被植入挖矿病毒的排查与复盘
1. 事件背景与异常发现
事件发生在一台部署了 Docker 服务的 NAS 服务器上。系统长期运行平稳,但近日发现系统负载(Load Average)异常飙升,长期维持在 18 以上,导致其他服务(如 Dockerd, QEMU)响应变慢。
通过 top 命令查看系统进程,发现以下异常特征:
异常进程名:两个名为
hash的进程长期驻留。资源占用:CPU 占用率高达 450%(多核累加),但单核线程似乎被限制在 80% 左右,未完全跑死系统。
用户权限:进程以
root权限运行。

2. 排查过程
2.1 定位进程实体
首先通过 PID(32691)定位进程的实体文件位置和启动参数:
ls -l /proc/32691/exe
# 结果指向:/app/hash
cat /proc/32691/cmdline
# 关键参数:-o auto.c3pool.org:13333 -u <钱包地址> --cpu-max-threads-hint=80 --randomx
分析结论:
进程是一个典型的 Monero (门罗币) 挖矿程序,使用 RandomX 算法。
--cpu-max-threads-hint=80参数解释了为何系统没有完全死机,这是病毒为了隐蔽自身、防止被管理员因卡顿察觉而设置的阈值。路径
/app/hash提示该进程极有可能运行在某个 Docker 容器内部。
2.2 溯源容器
由于 Docker 容器众多,需要精准定位宿主进程属于哪个容器。通过 cgroup 信息进行反查:
cat /proc/32691/cgroup
# 结果包含:docker-598de3114457...
通过 Container ID 598de3114457 比对 docker ps 列表,锁定目标容器为 nano-board。
这是一个我自己开发的简易服务器状态面板应用。
2.3 提取样本与取证
在停止容器但未删除的情况下,通过 docker cp 将病毒样本提取至宿主机进行分析:
文件属性:7MB 大小,ELF 64-bit LSB executable,静态链接(statically linked),去除了符号表(stripped)。
时间戳分析:
Modify时间为 2023-11-23,证明这是一个现成的、通用的 XMRig 二进制文件,而非本地编译。通过 NAS 历史性能曲线与进程运行时间(
9d+8h)推断,入侵时间点精确锁定在 12月1日。
VirusTotal 检测:样本哈希值
364a7f8e...在 VirusTotal 上检出率为 42/66,确认为Trojan.Linux.Miner.XMRig。

3. 入侵原因分析 (Root Cause Analysis)
核心问题:为何我自己写的软件会被黑?
经过复盘,攻击路径清晰如下:
暴露面扩大:12月1日,我申请并获得了动态公网 IP,并通过内网穿透工具将
nano-board的端口直接暴露在了公网。安全漏洞:
鉴权薄弱:服务可能存在弱口令或鉴权逻辑漏洞。
RCE 隐患:应用代码中可能存在命令执行(Remote Code Execution)漏洞(如不安全的
exec()调用),导致黑客可以通过 API 注入 Shell 命令。
权限过高:Docker 容器默认以
root用户运行。一旦黑客利用漏洞进入,即获得容器内的 Root 权限,可以随意使用wget下载病毒并赋予执行权限 (chmod +x)。
OverlayFS 机制注记:
病毒文件 /app/hash 位于 Docker 的 Copy-on-Write 层(读写层),并未挂载到宿主机 volume。因此,直接查看宿主机挂载目录无法找到病毒文件,必须在容器运行时或通过 docker inspect 查找 UpperDir 才能看到。
4. 损害评估与安全加固
为防止病毒逃逸(Container Escape),对宿主机进行了全面体检:
SSH 后门:检查
/root/.ssh/authorized_keys,未发现异常公钥。持久化后门:检查
crontab -l及/etc/crontab、/etc/cron.d/,未发现恶意定时任务。Docker API:检查 2375 端口,确认为关闭状态,未暴露。
结论:此次攻击属于典型的应用层入侵,病毒被成功隔离在容器内部,未对宿主机造成实质性渗透。
5. 解决方案与后续措施
清理现场:
提取样本留存后(用于分析),彻底删除病毒文件。
强制删除染毒容器及镜像:
docker rm -f nano-board && docker rmi ...。
代码修复:
审查代码中的命令执行逻辑,移除或加固所有
exec调用。增加强制的 HTTP Basic Auth 或 Token 验证。
最佳实践落地:
修改 Dockerfile,添加
USER node指令,禁止容器以 Root 身份运行。收缩公网暴露面,改用 VPN (如 WireGuard/EasyTier) 或 Nginx 反向代理访问内网服务。
6. 总结
这次经历是一堂生动的 DevSecOps 课。公网 IP 是一把双刃剑,在享受便利的同时,必须时刻警惕“黑暗森林”中的自动化扫描脚本。对于个人开发者而言,“能跑就行”的代码在公网环境下往往意味着“漏洞百出”。
教训: 永远不要信任用户输入,永远遵循最小权限原则(Least Privilege)。