Files
patbond-doc/docs/development/iterations/iteration-3.5/07-security-incident-20260911.md
T
lixi f9b1358b37
CI / docs-build (push) Successful in 1m59s
docs: 2026-09-11 安全事件复盘 + 新建服务器暴露面清单 + M3.5 报告 03/04/05 入档
安全事件(已闭环,服务恢复):
- 根因链: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>
2026-09-11 17:45:35 +08:00

83 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 安全事件复盘: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` 可被外部调用是否属已知漏洞待核,无论如何应保持不对公网监听)