· liyu · network-security · 27 min read · — 次浏览

合作伙伴的图片服务器沦陷记:一次 MinIO 挖矿攻击真实案例与完整攻击链还原

合作伙伴的图片资源服务器遭挖矿攻击:改完密码后秒级又被自动改回去、ls 看不到的文件却真实存在、伪装成 mysqld 的挖矿进程在磁盘上早已"消失"——本文以这次真实事件为样本,完整还原攻击者从 MinIO 弱口令进入、Root 提权、系统命令替换到 Watchdog 守护与后门持久化的七阶段攻击链,分析应急过程中的正确操作与三处失误,并给出从隔离、取证、格盘重装到凭据轮换的标准处置 SOP 与长效防范思路。

一、开篇:一个”闹鬼”的图片资源服务器

事情的起点,是合作伙伴的一台图片资源服务器——上面跑着 MinIO 对象存储,承载着业务的图片素材。某天它开始出现几个诡异的现象:

  • 服务器 CPU 长时间满载,load average 一度冲到 23+;
  • Docker 服务莫名其妙停了,docker ps 报 Cannot connect to the Docker daemon;
  • 我把 SSH 用户密码改掉,几分钟后密码又”自动恢复”成了旧的;
  • 更邪门的是,明明怀疑 /tmp 里有问题文件,ls /tmp 看起来却一切正常。

如果你只遇到第一条,那可能只是业务高峰;但四条同时出现,尤其是在改完密码又能被改回去之后,基本可以下一个结论了:

这不是”中了挖矿病毒”,这是一次 Root 权限级别的完整主机失陷(Full Compromise)。

这篇文章会完整还原这次事件的攻击链和背后的技术原理,复盘我应急处置时做对了什么、踩了哪些坑,最后给出标准的处置 SOP 和以后的防范思路。希望它既能当一篇攻击链分析,也能当一份可以照着做的应急手册。

二、先定性:为什么”杀掉挖矿进程”远远不够

在动手之前,先看一张现场证据与风险等级的对照表:

风险现场证据严重程度
Root 权限失陷可篡改 /etc/shadow、系统二进制极高
挖矿载荷/tmp/mysqld (deleted) 进程高
Watchdog 守护/61ab68db (deleted) 进程极高
系统命令替换ls / cat / rm 被替换为 1.3MB 恶意二进制极高
认证文件篡改修改密码后被自动恢复极高
远程控制后门伪装成 httpd 的 Perl 进程 + 异常 SSH 外联极高
业务干扰Docker 服务被停止高
数据泄露可能Root 权限理论上可读取全盘数据极高

这张表直接决定了处置策略:任何”找到恶意文件删掉就好”的思路都是错的。当攻击者拥有 Root、替换过系统工具、部署了守护进程和后门时,唯一可信的终点是:备份业务数据 → 保全证据 → 格盘重装。

三、攻击链全景:七阶段还原

根据现场证据,可以拼出攻击者的完整攻击路径:

互联网暴露的 MinIO 服务(弱口令 admin/admin123)
        │
        ▼
① 获取初始执行权限
        │
        ▼
② 提权至宿主机 Root(疑似经 Docker 攻击面)
        │
        ▼
③ 停止 Docker 服务 —— 独占 CPU 资源
        │
        ▼
④ 替换系统命令 ls / cat / rm —— Rootkit 隐藏
        │
        ├────────────────────────┐
        ▼                        ▼
⑤ 挖矿载荷                  ⑥ 持久化后门
/tmp/mysqld (deleted)       Perl 后门伪装 httpd
        │                   SSH 异常会话 + C2 外联
        ▼                        │
Watchdog 守护 ◄──────────────────┘
/61ab68db (deleted)
        │
        ├── 监控挖矿进程:被杀就重启
        └── 监控 /etc/shadow:改密码就写回
        │
        ▼
⑦ 连接矿池 / C2,长期稳定挖矿

需要严谨说明:“初始入侵是否一定通过 MinIO 进入”、“是否发生了 Docker 逃逸”属于基于现场证据的高可信推断,受感染系统的日志可信度本身存疑,无法百分之百回溯确认。但攻击链后段的每一个环节(工具替换、隐藏进程、Watchdog、后门、shadow 篡改)都有确凿的现场证据支撑。

下面逐阶段拆解技术原理。

四、阶段一:入口——一个 admin/admin123 引发的事故

事后检查 Docker 容器元数据(config.v2.json / hostconfig.json)时,发现了这样一个配置:

MINIO_ROOT_USER=admin
MINIO_ROOT_PASSWORD=admin123

同时这个 MinIO 容器还把宿主机目录直接挂了进去:

/mnt/minio-data:/data

这三行配置,每一行都是教科书级的反面教材:

  1. 弱口令:admin/admin123 出现在全球攻防团队的字典第一页,暴露在公网的 MinIO 控制台会被自动化工具在几分钟内爆破成功;
  2. 暴露面:管理端口直接对公网开放,等于把大门拆了;
  3. 宿主机目录挂载:一旦容器被攻破,攻击者顺手就能摸到宿主机文件系统,容器与宿主机之间的隔离形同虚设。

更重要的是,拿到 MinIO 管理权限通常意味着可以上传对象、触发服务端处理逻辑,在历史漏洞的加持下很容易过渡到”任意文件写入/命令执行”。虽然本事件中初始入侵路径无法 100% 锁定,但一个弱口令加暴露端口的组合,足以解释一切。

教训:公有云时代的攻击是全自动的。 攻击者不需要知道你是谁,扫描器 7×24 小时在互联网上转,admin123 被试中的时间以分钟计。

五、阶段二:为什么攻击者要停掉 Docker?

排查中发现 docker.service 处于异常停止状态。结合挖矿场景分析,这是攻击者的主动选择,目的有两个:

其一,独占 CPU。 一台物理机的算力是零和博弈:

停 Docker 前:MinIO + 业务容器 + 挖矿进程 → 三方抢 CPU,挖矿效率低
停 Docker 后:挖矿进程 独占全部核心 → 算力收益最大化

其二,干扰视线。 业务突然挂掉,管理员的注意力会立刻被”服务为什么停了”吸引,忙着重启 Docker、排查业务,反而没有精力去看真正的挖矿进程。这是一种典型的资源独占 + 业务干扰组合拳。

六、阶段三:Rootkit 的核心——替换系统命令

这是整个事件里最有意思、也最危险的部分。

6.1 发现:ls 怎么这么胖?

正常 Linux 中 ls 只有 100KB 出头,但现场发现:

/bin/ls、/usr/bin/ls        → 约 1.3 MB
/usr/bin/opentr             → 约 146 KB,strings 可见 GNU Coreutils 字符串

真相一目了然:攻击者把原版 ls 改名成了 opentr 藏起来,然后把一个约 1.3MB 的恶意 Wrapper 放到 /usr/bin/ls 的位置。而且修改时间 2026-04-15 16:45:03 在 opentr、ls、zipcat、ziprm 多个文件上完全一致——这是同一个批量脚本一次性完成的。

6.2 恶意 ls 能做什么?

管理员执行: ls /tmp
恶意 ls:    读目录 → 过滤黑名单文件名(mysqld / xmrig / 61ab68db ...)→ 输出
管理员看到: 干干净净,什么都没有
实际磁盘:   /tmp/mysqld 好端端躺在那里

这就是典型的用户态 Rootkit:不修改内核,只劫持你观察世界的眼睛。同样的思路可以作用于 ps(隐藏进程)、netstat(隐藏连接)、cat(读文件时篡改内容)、rm(删除恶意文件时假装成功)。

6.3 由此推出应急响应的第一原则

一旦怀疑 Rootkit,默认这台机器上所有用户态工具都不可信:ls、ps、top、netstat、ss、cat、find、rm,一个都别信。

那还能信什么?后面会讲,/proc 是这次事件中的破局点。

七、阶段四:落地即删——(deleted) 背后的进程隐藏术

通过 /proc 排查时,发现了本次事件最关键的两个进程:

PID 2269 → /tmp/mysqld (deleted)
PID 2116 → /61ab68db (deleted)

(deleted) 是什么意思?

Linux 中进程启动后,可执行文件会被加载进内存并保持打开。此时即使执行 unlink() 把磁盘文件删掉——目录项消失,但进程依然正常运行:

execve("/tmp/mysqld")
        ↓
文件被加载、保持打开
        ↓
unlink("/tmp/mysqld")   ← 磁盘上文件消失
        ↓
进程照常运行,内存中代码完整

这套”落地即删(Unlink Evasion)“的组合效果极佳:

  1. 伪装名 mysqld——看起来像 MySQL 数据库进程,谁会对 mysqld 产生怀疑?
  2. 磁盘文件已删除——ls /tmp 找不到任何可疑文件,文件扫描器扑空;
  3. 只有 /proc/<PID>/exe 会诚实地显示 /tmp/mysqld (deleted)。

从恶意二进制中提取到的 transparent_hugepage、hpage_pmd_size 等字符串,与 XMRig 类门罗币挖矿程序利用 Linux 大页内存做性能优化的特征高度吻合,可以基本判定这是一个 CPU 挖矿载荷。

八、阶段五:Watchdog——为什么密码改了会”自己变回去”

/61ab68db (deleted) 这个进程,才是整条攻击链里真正的”钉子”。它是一个守护程序(Watchdog),干两件事:

第一件,守护挖矿进程:

循环检测: /tmp/mysqld 还活着吗?
        ├── 活着 → sleep 继续监控
        └── 被杀 → 重新拉起

第二件,监控认证文件——这解释了开头那个”闹鬼”现象:

管理员执行 passwd 修改密码
        ↓
/etc/shadow 内容变化
        ↓
Watchdog 检测到 shadow 变更
        ↓
重新写入攻击者已掌握哈希的条目
        ↓
几分钟后,密码"自己"变了回去

这属于认证文件持久化篡改。很多人排查时会反复尝试”再改一次密码”,但只要 Watchdog 还活着,改多少次都是徒劳——你根本没在跟”密码”作战,你是在跟一个 Root 权限的守护进程作战。

九、阶段六:Perl 后门与异常 SSH 会话

排查 /proc 时还发现了一个伪装高手:

PID 1403 → /usr/bin/perl,进程名伪装成 usr/sbin/httpd,工作目录 /

一个”长得很像 Web 服务器”的 Perl 进程,高度疑似反弹 Shell / 交互式后门。它的战略意义远超挖矿本身:

攻击者 → C2 服务器 → Perl 后门 → Root Shell

也就是说,就算管理员发现并杀掉了挖矿进程、改掉了所有密码,只要这个后门还在,攻击者随时可以卷土重来。挖矿载荷只是”赚钱工具”,后门才是攻击者的”房产证”。

网络侧还通过 /proc/net/tcp 发现了两条 SSH 相关外联:

  • 49.93.125.47 —— 后来确认是管理员自己的公网出口 IP;
  • 122.96.37.92 —— 高度可疑的攻击者 SSH 会话。

这里埋了一个我后来踩的大坑,应急篇细说。

十、七层防御体系:攻击者为什么设计得这么复杂?

把上面所有环节叠起来看,攻击者构建了一个接近完整的 Linux 主机入侵工具链:

层级手段目的
第一层:伪装挖矿程序命名 mysqld混淆视听
第二层:隐藏替换 ls/cat/rm + 落地即删文件与进程不可见
第三层:自恢复Watchdog杀了就重启
第四层:权限维持Perl 后门 + SSH 后门随时回来
第五层:反管理监控 /etc/shadow 写回密码改密码无效
第六层:反分析磁盘文件全部删除取证困难
第七层:资源独占停止 Docker算力收益最大化

单纯跑一个 xmrig 太容易被发现。攻击者真正的目标是:把这台服务器变成一个长期、稳定、无人察觉的矿机。每一层设计都在对抗管理员的一种处置手段。

十一、应急处置复盘:做对了什么

11.1 先保业务数据,没有上来就格盘

第一时间完成了 MinIO 数据(/mnt/minio-data)和 Nginx 配置(/etc/nginx)的备份,并从 Docker 元数据中恢复出了完整的容器重建信息:

镜像:   minio/minio:RELEASE.2025-04-22T22-12-26Z-cpuv1
挂载:   /mnt/minio-data:/data
端口:   宿主机 90 → 9000,9001 → 9001

应急处置不能以牺牲业务数据为代价,这一点上的顺序是对的。

11.2 用 /proc 绕过 Rootkit 的欺骗

这是本次处置最关键的一步。既然 ls 和 ps 都可能说谎,那就直接问内核——/proc 文件系统是内核真实状态的投影:

# 遍历所有进程,查看真实可执行文件路径
for pid in /proc/[0-9]*; do
  exe=$(readlink -f "$pid/exe" 2>/dev/null)
  case "$exe" in
    *deleted*) echo "可疑: PID $(basename $pid) → $exe" ;;
  esac
done

输出直接锁定了两个目标:

[x] PID 2116 → /61ab68db (deleted)   ← Watchdog
[x] PID 2269 → /tmp/mysqld (deleted) ← 挖矿主进程

11.3 标准的两步击杀:先冻结,再击杀

kill -STOP 2116 2269   # 先冻结,防止 Watchdog 在间隙拉起矿机
kill -9 2116 2269      # 确认冻结后彻底终止

先 STOP 后 KILL 的顺序很重要:如果直接 kill -9 挖矿进程,Watchdog 可能抢在你杀它之前把矿机拉起来;先冻结两者,再逐个击杀,才能避免”按下葫芦浮起瓢”。执行完毕后系统负载开始回落。

11.4 但——杀掉进程 ≠ 服务器安全了

这是本次事件最重要的结论。攻击者已经完成 Root 权限下的持久化布局,即使两大核心进程被清掉,仍可能存在未被发现的后门:

SSH authorized_keys / Cron / systemd 单元 / PAM 模块
LD_PRELOAD / 内核模块 / /etc/rc.local / .bashrc
隐藏 ELF / WebShell / 其他后门用户 ...

“CPU 降到 0”只能证明矿机停了,不能证明房子里没有别的贼。

十二、踩过的坑:三次失误与正确做法

应急过程不可能完美,把错误也写下来,比只写成功更有价值。

坑一:用了攻击者留下的”原版备份工具”

现场发现 zipcat、ziprm 两个文件,我最初判断它们是攻击者替换工具时顺手备份的原版 cat/rm,于是直接复制回去恢复系统命令。

结果它们同样是约 1.3MB 的恶意二进制。 执行后立刻出现 Too many open files、Shell level 1000,CPU 负载再次暴涨——恶意程序在递归 fork:

cat → 恶意 Wrapper → bash → bash → bash → ... → 1000 层

文件描述符和 pipe 资源瞬间耗尽。这是一个非常典型的教训:

永远不要使用攻击者留在受害机上的”备份”来恢复系统。 正确做法是从可信来源重装软件包(Ubuntu 下 apt-get install --reinstall coreutils,且最好在隔离环境下进行),或直接使用可信 Live CD / 救援系统操作。

坑二:没确认自己的 IP 就执行了 ss -K

发现可疑外联后,我直接执行了:

ss -K dst 49.93.125.47

下一秒,自己的 SSH 会话被掐断了——因为 49.93.125.47 后来确认是管理员自己的公网出口 IP。ss -K 成功地杀掉了……我自己的 TCP 连接。

正确流程应该是:

① 先确认自己的公网出口 IP(curl ifconfig.me 之类)
② 再区分攻击者 IP 与自身 IP
③ 确认 SSH 会话对应的 PID
④ 最后再 kill / 封禁

坑三:iptables 执行失败却没有验证结果

尝试添加封禁规则时实际返回了 iptables: command not found——规则从未写入,但后续处置逻辑仍按”22 端口已封禁”继续推进。正确原则:

任何安全控制动作,必须检查命令返回结果。 “执行了封禁命令”和”封禁生效”是两回事。Ubuntu 上还要先搞清楚系统用的是 iptables、nft 还是 ufw。

坑四(方法论层面):取证顺序颠倒了

更理想的顺序是”先取证、后处置”:

发现入侵 → 隔离网络 → 保存内存/进程样本 → 保存日志
→ 记录网络连接 → 保存恶意文件 → 最后才杀进程

本次是”发现即处置、边处置边调查”,作为业务优先的止血策略可以理解,但如果事件需要溯源定责或司法取证,这个顺序会丢失大量关键证据(进程被杀、内存消失后,(deleted) 背后的样本就再也拿不回来了)。

十三、正确的完整处置 SOP:从隔离到恢复公网

结合本次经验,整理出一套可复用的标准流程:

P0 判定 ──── CPU 异常 + 密码自动恢复 + 可疑文件 → 直接按"Root 失陷"处置
P1 隔离 ──── 优先物理断网 / 交换机 ACL / 安全组(在受害机之外操作)
P2 可信调查 ─ 不信任受害机工具,使用 IPMI / iLO / Live CD / 救援系统
P3 保全证据 ─ /proc 样本、/var/log、crontab、systemd、SSH、网络连接、恶意 ELF
P4 备份业务 ─ 数据库、MinIO、静态资源、源码、配置、证书(备份后需在干净机器复查)
P5 彻底重装 ─ 格盘 → 全新安装 OS → 更新全部软件包 → 重建 Docker / Nginx
P6 凭据轮换 ─ 见下节清单,全部换掉
P7 恢复业务 ─ MinIO → 数据库 → Docker → Nginx → Web/API
P8 加固收尾 ─ 防火墙 + SSH 加固 + fail2ban + 自动更新 + 监控,最后才开放公网

13.1 为什么必须格盘重装?

因为**“清除了已发现的文件”无法证明”系统干净”**。你永远不知道攻击者是否还留了别的后门:一个 PAM 模块、一个 LD_PRELOAD、一个内核模块、一个被改过的 sshd。在这种确定性不对称面前:

重装的成本是确定的几小时,“相信自己已经清干净”的风险是不确定的无限大。

13.2 重装后的凭据轮换清单

Root 失陷意味着攻击者理论上读过全盘数据。以下凭据全部视为已泄露,必须轮换:

SSH 密钥对          → 重新生成,废弃旧 Key,清查 authorized_keys
Linux 用户密码      → 全部重置
MinIO Access/Secret → 重建(admin/admin123 永久作废)
MySQL / Redis 密码  → 全部更换
JWT Secret          → 更换(会使所有已发 token 失效)
第三方 API Key / OSS Key / SMTP / Git Token → 逐个轮换
SSL 证书私钥        → 重新申请签发,不要原样恢复旧私钥

十四、防范思路:下次别再给攻击者送分

复盘整条攻击链会发现,攻击者的每一步都利用了”低成本配置 + 缺失监控”。防范也只需要对症下药:

14.1 收缩攻击面(掐断阶段一)

  • 消灭弱口令:所有暴露服务强制强密码/密钥认证,admin123 这类口令直接禁止;
  • 最小暴露面:MinIO 控制台、数据库、Redis 等管理端口一律不对公网开放,必须访问就走 VPN / 堡垒机 / IP 白名单;
  • SSH 加固:禁用密码登录、仅密钥认证、禁用 root 直接登录、fail2ban 自动封禁爆破 IP。

14.2 容器安全(掐断阶段二)

  • 容器不加 --privileged、不挂载 /var/run/docker.sock;
  • 尽量避免把宿主机敏感目录直接挂进容器,确需挂载用只读(:ro);
  • 保持 Docker 与内核及时更新,堵住已知逃逸漏洞。

14.3 纵深检测(让阶段三~六无处藏身)

  • 文件完整性监控:AIDE / osquery 对 /usr/bin、/bin 建立基线,系统命令被替换立刻告警;
  • 关键文件监控:用 auditd / inotify 监控 /etc/shadow、/etc/passwd、authorized_keys 的变更——本次事件的”密码自动恢复”如果有 shadow 变更告警,第一时间就能暴露;
  • 异常进程监控:对”名字像系统服务、路径却在 /tmp”、/proc/<PID>/exe 指向 (deleted) 的进程直接告警;
  • 外联监控:服务器通常不需要主动外连陌生 IP,对矿池协议端口(如 3333、4444、5555)出站封禁。

14.4 降低爆炸半径

  • 业务容器与宿主机服务资源隔离,挖矿程序无法一句话停掉 Docker 独占资源;
  • 最小权限原则:业务进程不要用 root 跑,被攻破时攻击者还要再做一次提权;
  • 定期快照 + 异地备份,让”格盘重装”的底气更足。

十五、写在最后:十条值得记住的经验

  1. Root 权限失陷后,不要再相信这台机器上的任何东西。
  2. ls 能骗你,ps 也能骗你;/proc 是内核留下的诚实入口。
  3. (deleted) 不代表消失——落地即删是挖矿木马的标配隐身术。
  4. /tmp/mysqld 可以根本不是 MySQL。 进程名是最廉价的伪装。
  5. 密码改完又自动变回去,是最强的持久化入侵信号,没有之一。
  6. 攻击者最值钱的不是挖矿程序,而是 Watchdog 和后门。
  7. 攻击者留下的”原版工具备份”,本身就是武器。
  8. 杀掉挖矿进程 ≠ 清除入侵;CPU 恢复正常 ≠ 服务器干净。
  9. 动手封 IP 之前,先确认哪个 IP 是你自己的。
  10. Root 失陷的生产服务器,格盘重装永远比”我觉得我清干净了”更安全。

这次事件的处置有亮点也有失误,但最大的收获是想明白了一个道理:安全攻防的本质不是”找到一个恶意文件然后删掉”,而是判断系统的信任边界是否已经被击穿。一旦 Root 沦陷,信任模型就崩塌了——此时最专业的选择不是恋战,而是快速保全数据与证据,然后在一片干净的地基上重建。

愿你的服务器永远用不上这篇文章。

Share:

回看感悟累计回看 — 次

加载中…

Back to Blog

Related Posts

View All Posts »

从键入网址到屏幕亮起:硬核拆解一次网络请求的奇幻漂流

当你在浏览器地址栏输入 URL 并按下回车,到网页绚丽呈现,这背后究竟经历了什么?从 HTTP 报文构建、DNS 分级解析,到操作系统内核协议栈封包、ARP 寻址,再到网卡、交换机、路由器的物理传输与服务端响应、浏览器渲染,本文带你完成一次穿透软硬件的全景探秘。

文本向量化演进:从 Word2Vec 到现代 Embedding 模型

文本向量化是搜索、推荐与 RAG 共同的地基。本文快速回顾 one-hot、TF-IDF、Word2Vec 等经典方案的思路与局限,重点拆解现代 Embedding 模型的三大支柱:BERT 上下文表示、Sentence-BERT 句向量专用化与对比学习训练范式,并盘点 BGE、GTE、E5、OpenAI text-embedding-3 等主流模型与 MRL、指令前缀等关键技术。

TF-IDF 原理与应用:搜索打分与文本向量化的起点

TF-IDF 用「词频 × 稀缺度」给词打分,是信息检索绕不开的经典。本文快速讲透它的核心原理,重点展开两大应用:搜索系统里的相关性打分,以及把文本变成稀疏向量后的分类、相似度与关键词提取。

RRF 倒数排名融合:混合检索里的"排名仲裁者"

向量语义召回与 BM25 关键词召回的分数天然不可比,混合检索需要一个仲裁者。本文以混合检索为例讲解 RRF(Reciprocal Rank Fusion)算法的公式与 k 常数的数学直觉,并横向对比分数级融合、名次级融合与学习排序三类融合策略的取舍。