Repo .git was damaged (refs/ missing, index truncated, object store near-empty). Repaired via git init (rebuilt refs), stale index preserved as .git/index.corrupt-20260910.bak, then full worktree committed as a rollback baseline.
6.7 KiB
6.7 KiB
TamaBox-v4 — P0 替代方案 + P2 整改(除 P2-4)与复审
- 日期:2026-09-01
- 范围:P0 IP 伪造的「免 CIDR」替代方案落地;P2-1
P2-3、P2-5P2-14(除 P2-4)全部改进;改动后复审。 - 方法:静态走查 +
go build/go vet/go test(受 webp CGO 沙箱限制,含internal/storage的包仅做gofmt -e语法校验)。
一、P0 的「免 CIDR」替代方案(你最关心的点)
原报告建议「维护 Cloudflare 全量 CIDR 白名单 + 自右向左剥离跳数」。但正如你指出的:CIDR 列表太大、且 Cloudflare 边缘节点会变动,维护成本高且易遗漏。本次采用 不依赖任何 CDN CIDR 清单 的方案:
设计核心(internal/shared/ip.go)
- 优先采用
CF-Connecting-IP(Cloudflare 权威头)。只要源站被 锁定只接受 Cloudflare 连接,该头即等于真实客户端 IP,无需任何 IP 白名单。 trusted_proxies仅用于你自己的基础设施(如本机 nginx127.0.0.1、私有10.0.0.0/8LB),默认空。仅当 TCP 对端命中该小清单时才解析X-Forwarded-For/X-Real-IP,且从右向左剥离已知可信跳数后取原始客户端。- 其余情况一律回退到
RemoteAddr,绝不信任客户端自行提供的头。
为什么这能根治,且不用 CIDR
- 关键在部署侧锁定源站,推荐二选一(都不需要 CIDR 清单):
- Cloudflare Tunnel(
cloudflared):源站无任何公网 IP,只与 Cloudflare 建隧道 → 攻击者无法直连源站伪造CF-Connecting-IP。 - Cloudflare Authenticated Origin Pull(mTLS):源站 TLS 要求 Cloudflare 客户端证书 → 非 Cloudflare 连接被拒。
- Cloudflare Tunnel(
- 二者都让「伪造
CF-Connecting-IP」在物理/密码学层面不可行,从而免去了维护庞大 CIDR 的负担。
配置变更
conf/static.go:Security新增TrustedProxies string \ini:"trusted_proxies"``。conf/app.sample.ini:[security]下新增trusted_proxies =(默认空),并改写trust_proxy注释,明确「开启 trust_proxy 必须配合 Tunnel/mTLS 锁定源站」。internal/cmd/web.go:conf.Init()后调用shared.InitTrustedProxies()解析清单(解析失败则启动报错)。
残留风险(已文档化):若
trust_proxy=true但源站仍可被直连(未锁源站),攻击者可自行发CF-Connecting-IP被信任。生产必须配合上述锁定手段。这是「免 CIDR」方案的必要前提。
二、P2 整改清单(除 P2-4 外全部完成)
| # | 位置 | 改动 |
|---|---|---|
| P2-1 | route/vote/vote.go:310 |
排序题统计 json.Unmarshal(s.FormData, &data.Ranking) → &data,修复解析必失败、排名恒为 0 的 bug |
| P2-2 | route/vote/vote.go + route/user/vote.go |
抽离 InvalidateResultsCache(voteID);除新提交外,草稿「选项/字段重建」「投票删除」也主动失效结果缓存 |
| P2-3 | internal/db/vote_submissions.go |
DeleteByVoteID 改为 Unscoped() 物理删除;避免软删行残留占据唯一索引导致「统计归零却无法重投」 |
| P2-5 | internal/db/questions.go + follow_ups.go + upload_images.go |
删除提问时级联软删其 follow_ups 与 upload_images,消除孤儿记录 |
| P2-6 | internal/db/users.go:814 |
CountTodayMessagesFromUser 改用应用本地时区当日 00:00 边界,替代 DB 的 CURRENT_DATE,修正每日私信上限窗口偏移 |
| P2-7 | route/user/profile.go |
导出 XLSX 单元格前置单引号防御公式注入(以 = + - @ \t \r \n 开头的内容按纯文本处理) |
| P2-8 | internal/db/users.go:244 |
注册 Create 遇重复键错误时重新执行 validate 返回友好错误(ErrDuplicateEmail/ErrDuplicateDomain),消除 TOCTOU 暴露底层 DB 错误 |
| P2-9 | internal/route/gzip.go |
Write 将嗅探到的 Content-Type 写回响应头使压缩判定生效;204/304 不再附加 Content-Encoding |
| P2-10 | internal/route/route.go:244 |
/i18n/{lang} 重定向拦截反斜杠 \\ 与控制字符,防开放重定向变体 |
| P2-11 | internal/route/route.go |
加 Strict-Transport-Security(仅 https 时下发);lang cookie 在 https 下设 Secure |
| P2-12 | internal/route/chat/chat_handler.go |
SendMessage 发送前重新校验 FeatureStateOf(FeatureChat).Open,箱主中途关闭私聊即阻止继续发消息 |
| P2-13 | internal/db/questions.go + route/question/page.go |
置顶优先分页改用复合键集游标 (is_pinned, created_at, id),消除置顶项与非置顶项 id 交错导致的「加载更多」漏项 |
| P2-14 | internal/captcha/math.go |
已由前次 P1-3 覆盖:rand.Int 错误已校验(无新增改动) |
P2-4(Question.Token 仅 6 位/唯一索引)按你要求跳过。
三、复审验证结果
| 项目 | 结果 |
|---|---|
go build 不依赖 storage 的包(shared/db/captcha/security/censor) |
✅ 全绿 |
go vet 同上包 |
✅ 无告警 |
go test ./route/vote/... |
✅ 通过(覆盖 P2-1 排名聚合、缓存接口一致) |
gofmt -e 受 webp 影响的编辑文件(route/user/*、route/question/page.go、internal/route/*、chat_handler.go) |
✅ 均无语法错误 |
route/vote 整包 go build |
✅ 通过(P2-1/P2-2 改动类型校验通过) |
⚠️ 含 internal/storage(webp CGO)的包(internal/route、route/user、route/question、internal/route/chat、cmd/web)本沙箱无法完整编译(既定限制,与改动无关)。这些文件已用 gofmt -e 确认无语法错误,需在 Linux / 带 libwebp 环境跑一次 go build ./... 做最终确认。
复审新发现 / 残留项
- P2-2 多实例一致性:本方案让各实例在「新提交 / 改选项 / 删投票」时各自失效自身进程内缓存,但跨实例仍依赖 30s TTL 自然过期(进程内 TTL 缓存的固有限制)。若未来做多实例横向扩容,建议将
resultsCache迁到 Redis 共享缓存,方能做到即时全局一致。当前单实例/低并发场景已足够。 - P2-5 级联为软删除:与提问本身的软删除保持一致;对象存储中的实际图片文件未删除(避免误删复用资源),仅清 DB 记录。若需彻底清理文件,应另起异步任务。
- 其余改动逻辑自洽,未发现引入新的越权 / 竞态 / 注入点。
四、待你确认/执行的部署动作
- P0 生效前提:生产环境用 Cloudflare Tunnel 或 Authenticated Origin Pull 锁定源站;并在
app.ini设trust_proxy = true、trusted_proxies填入你自己的反代 IP(如127.0.0.1)。 - 最终构建:在带 libwebp 的环境
go build ./...验证全部包。