f9b1358b37
CI / docs-build (push) Successful in 1m59s
安全事件(已闭环,服务恢复): - 根因链:Gitea 3000 对公网开放 → 外部调用 /api/internal/manager/add-logger 注入 gitconfig 的 uploadpack.packObjectsHook → 指向不存在的脚本 → upload-pack 发 NAK 后无法产出 pack → 全仓 HTTPS clone 失败(CI 全挂) - 攻击未达成代码执行(hook 目标脚本不存在);三仓 ref 与本地逐一核对未被篡改; 无系统层入侵(无陌生 key/crontab/挖矿进程/陌生登录) - 新建常设「服务器暴露面清单」:补上服务器侧「决策变了环境没跟上」的核对机制 (Nacos 在 ADR-002 移除后仍暴露公网近两个月) - CI Runner 手册排障表增三条:CI 秒失败先在本机复现 checkout、跨仓比 CI 须核对时间戳、clone 坏而 push 正常时查 packObjectsHook 注入 M3.5 交付报告:03 后端资料与头像(api 334→379)、04 契约冻结 v1.4.0 (31→32 路径、矩阵 173→181 格、11 格红转绿)、05 资料页与头像 UI(flutter 526→597) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
83 lines
6.1 KiB
Markdown
83 lines
6.1 KiB
Markdown
# 安全事件复盘:Gitea gitconfig 注入(2026-09-11)
|
||
|
||
**级别**:中(服务中断,无数据损失,攻击未达成代码执行)
|
||
**发现方式**:CI 连续失败排查
|
||
**处置结果**:已闭环,服务恢复
|
||
**记录人**:主会话(AI 辅助排查,用户执行服务器侧操作)
|
||
|
||
---
|
||
|
||
## 1. 时间线(UTC+8)
|
||
|
||
| 时间 | 事件 |
|
||
| --- | --- |
|
||
| 2026-07-13 | Nacos 部署于服务器并对公网暴露 8848/9848(此前 ADR-002 已将 Nacos 从项目移除,属遗留服务) |
|
||
| 2026-09-10 18:04 | flutter CI 最后一次成功(task 68) |
|
||
| 2026-09-10 夜 ~ 09-11 晨 | **攻击发生**:外部 IP 调用 Gitea 内部管理 API 写入 gitconfig |
|
||
| 2026-09-11 09:27 | doc 仓 CI 首次失败(1 秒,无 step) |
|
||
| 2026-09-11 10:03 / 10:33 | api 仓 CI 失败;期间另有 4 个提交的 job 完全未上报状态 |
|
||
| 2026-09-11 下午 | 定位、处置、验证恢复 |
|
||
|
||
攻击者来源 IP(gitconfig 注入串中残留):`20.212.233.41`(Azure 段)、`187.15.89.220`(巴西)。
|
||
|
||
## 2. 根因链
|
||
|
||
1. **Gitea 的 3000 端口对公网开放**(`HTTP_ADDR` 未限制为 127.0.0.1,且轻量服务器防火墙放行 3000),使 nginx 之外存在一条直达通道。
|
||
2. **`/api/internal/**` 可被外部调用**:攻击者 `POST /api/internal/manager/add-logger`,利用日志路径参数把内容写进 Gitea 的 gitconfig(注入串以 `;#` 收尾,用于把 Gitea 追加的日志行注释掉)。
|
||
3. **注入项为 `uploadpack.packObjectsHook`**,指向 `/var/lib/gitea/data/home/p0_*.sh` 等文件。该 hook 是 `git upload-pack` 生成 pack 时调用的外部程序。
|
||
4. **脚本并不存在** → hook 执行失败 → upload-pack 在发出 `NAK` 后无法产出任何 pack 数据 → **所有 HTTPS clone/fetch 失败**,而 Gitea 仍记为 `200 OK in 3ms`。
|
||
|
||
被污染的两份文件:`/var/lib/gitea/data/home/.gitconfig`、`/var/lib/gitea/home/.gitconfig`。
|
||
|
||
## 3. 影响评估
|
||
|
||
| 面 | 结论 | 依据 |
|
||
| --- | --- | --- |
|
||
| **代码完整性** | ✅ 未被篡改 | 三仓 `git ls-remote` 的 dev/main/tag 与本地权威值逐一核对一致(api dev `3cd8005`、main `8089c06`、flutter dev `6945436`、main `0e87413`、doc main `5f02909`,三个 v0.3.0 tag 全一致) |
|
||
| **攻击是否达成代码执行** | ✅ 未达成 | hook 指向的 `p0_*.sh` 经确认**不存在**;正因执行失败才暴露事件 |
|
||
| **系统是否被入侵** | ✅ 未被入侵 | `authorized_keys` 仅 2 个已知 key(腾讯云 skey + 维护者本人);ubuntu 无 crontab、root crontab 仅腾讯云 agent;无挖矿进程(最高 CPU 1.0% 为云监控 agent);登录记录全部来自维护者常用 IP 段;`Failed password` 仅 1 次 |
|
||
| **开发是否受影响** | ✅ 未受影响 | push 走 SSH 且 `receive-pack`(写入方向)不经 `packObjectsHook`,故两日内所有提交正常落地 |
|
||
| **CI** | ❌ 中断约 1 天 | checkout 走 HTTPS,全仓失效 |
|
||
| **凭证泄露风险** | ⚠️ 存在 | 攻击者能调用内部 API,`INTERNAL_TOKEN` 须视为可能泄露并轮换 |
|
||
|
||
## 4. 处置动作
|
||
|
||
| # | 动作 | 状态 |
|
||
| --- | --- | --- |
|
||
| 1 | Gitea `HTTP_ADDR = 127.0.0.1`(不再对公网监听),重启 | ✅ |
|
||
| 2 | 防火墙删除 3000、8848、9848、2222 放行规则 | ✅ |
|
||
| 3 | 停止 Nacos 并确认无监听(`nacos.service` 需一并 disable 防重启自启) | ✅ 停止;⚠️ disable 待确认 |
|
||
| 4 | 清理两份 gitconfig 的 `uploadpack.packObjectsHook`(原文件已备份至 `/root/gitconfig*.evidence.*.bak`) | ✅ |
|
||
| 5 | 重启 Gitea 并验证恢复:三仓 HTTPS 浅克隆全成功;upload-pack 响应从 12 字节恢复至 723 KB | ✅ |
|
||
| 6 | 轮换 `INTERNAL_TOKEN`(及可选 `SECRET_KEY`) | ⬜ 待做 |
|
||
| 7 | nginx 增加 `location ^~ /api/internal/ { deny all; return 404; }` 作为纵深防御 | ⬜ 待做 |
|
||
| 8 | 清理 gitconfig 中残留的注入垃圾注释行 | ⬜ 待做(不影响功能) |
|
||
|
||
## 5. 诊断过程的教训(比结论更值得记)
|
||
|
||
**判断走过两次弯路**,都源于采信推断而非实测:
|
||
|
||
1. **第一次误判「act_runner 故障」**:CI 日志显示 job 1~2 秒失败、`steps: null`,据此推断 runner 挂了。实际 runner 容器 Up 6 天、job 镜像在本地、job 容器正常启动。
|
||
2. **第二次误判「flutter CI 正常所以 runner 活着」**:查到 flutter 最新提交 CI 为 success,却**没核对时间戳**——那是前一天 18:04 的旧记录。跨仓比较必须带时间戳。
|
||
3. **第三次误判「nginx 代理层」**:绕过 nginx 直连 3000 后同样失败,才排除。
|
||
|
||
**真正的转折点是两个动作**:
|
||
- **在本机复现同样的 git 操作**(`git clone --depth 1 https://...` 立刻复现 EOF)→ 一举把问题从「CI 领域」移到「Gitea 服务端领域」;
|
||
- **手动执行 Gitea 内部实际调用的命令**(`git upload-pack --stateless-rpc`)→ 手动成功、进程内失败,把差异锁定到执行环境,于是查 gitconfig 时一眼看到注入。
|
||
|
||
**沉淀为纪律(已写入 [CI Runner 手册](../../ci-runner-setup.md) 排障表)**:CI 失败时,第一动作是**在本机复现 CI 的第一个 step**(通常是 checkout),而不是先去查 runner。
|
||
|
||
## 6. 更深层的问题:环境漂移无人核对
|
||
|
||
本次真正的隐患不是「Gitea 有个洞」,而是 **Nacos 在项目已用 ADR-002 明确移除后,其进程与防火墙规则仍在服务器上暴露公网近两个月**。
|
||
|
||
代码侧我们有 ADR + 契约冻结 + 契约一致性测试来防止「决策变了、实现没跟上」,**服务器侧却没有任何对应机制**。为此新建 [服务器暴露面清单](../../server-exposure.md):逐项登记开放端口与常驻服务的用途、归属决策、最后确认日期,作为常设文档定期核对。
|
||
|
||
## 7. 待办
|
||
|
||
- [ ] 轮换 `INTERNAL_TOKEN`
|
||
- [ ] nginx 拒绝 `/api/internal/`
|
||
- [ ] `systemctl disable nacos.service`(当前仅停止,仍为 enabled,重启会自启)
|
||
- [ ] 清理 gitconfig 残留注释垃圾行
|
||
- [ ] Gitea 升级评估(当前 1.26.4;`/api/internal` 可被外部调用是否属已知漏洞待核,无论如何应保持不对公网监听)
|