# 安全事件复盘: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` 可被外部调用是否属已知漏洞待核,无论如何应保持不对公网监听)