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>
6.1 KiB
6.1 KiB
安全事件复盘: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. 根因链
- Gitea 的 3000 端口对公网开放(
HTTP_ADDR未限制为 127.0.0.1,且轻量服务器防火墙放行 3000),使 nginx 之外存在一条直达通道。 /api/internal/**可被外部调用:攻击者POST /api/internal/manager/add-logger,利用日志路径参数把内容写进 Gitea 的 gitconfig(注入串以;#收尾,用于把 Gitea 追加的日志行注释掉)。- 注入项为
uploadpack.packObjectsHook,指向/var/lib/gitea/data/home/p0_*.sh等文件。该 hook 是git upload-pack生成 pack 时调用的外部程序。 - 脚本并不存在 → 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. 诊断过程的教训(比结论更值得记)
判断走过两次弯路,都源于采信推断而非实测:
- 第一次误判「act_runner 故障」:CI 日志显示 job 1~2 秒失败、
steps: null,据此推断 runner 挂了。实际 runner 容器 Up 6 天、job 镜像在本地、job 容器正常启动。 - 第二次误判「flutter CI 正常所以 runner 活着」:查到 flutter 最新提交 CI 为 success,却没核对时间戳——那是前一天 18:04 的旧记录。跨仓比较必须带时间戳。
- 第三次误判「nginx 代理层」:绕过 nginx 直连 3000 后同样失败,才排除。
真正的转折点是两个动作:
- 在本机复现同样的 git 操作(
git clone --depth 1 https://...立刻复现 EOF)→ 一举把问题从「CI 领域」移到「Gitea 服务端领域」; - 手动执行 Gitea 内部实际调用的命令(
git upload-pack --stateless-rpc)→ 手动成功、进程内失败,把差异锁定到执行环境,于是查 gitconfig 时一眼看到注入。
沉淀为纪律(已写入 CI Runner 手册 排障表):CI 失败时,第一动作是在本机复现 CI 的第一个 step(通常是 checkout),而不是先去查 runner。
6. 更深层的问题:环境漂移无人核对
本次真正的隐患不是「Gitea 有个洞」,而是 Nacos 在项目已用 ADR-002 明确移除后,其进程与防火墙规则仍在服务器上暴露公网近两个月。
代码侧我们有 ADR + 契约冻结 + 契约一致性测试来防止「决策变了、实现没跟上」,服务器侧却没有任何对应机制。为此新建 服务器暴露面清单:逐项登记开放端口与常驻服务的用途、归属决策、最后确认日期,作为常设文档定期核对。
7. 待办
- 轮换
INTERNAL_TOKEN - nginx 拒绝
/api/internal/ systemctl disable nacos.service(当前仅停止,仍为 enabled,重启会自启)- 清理 gitconfig 残留注释垃圾行
- Gitea 升级评估(当前 1.26.4;
/api/internal可被外部调用是否属已知漏洞待核,无论如何应保持不对公网监听)