7.0 KiB
7.0 KiB
TamaBox-v4 修复后代码复审报告
- 复审时间:2026-08-26
- 审阅对象:TamaBox-v4
- 复审基线:
outputs/TamaBox-v4-code-review-report-2026-08-26.md - 复审范围:上一轮 4 项确认问题的修复调用链、相关数据库模型、模板与回归测试;并对修复影响面做定向静态复查。
- 结论口径:本报告仅将能由当前代码直接确认的问题列为确认问题;环境限制与建议性事项单独列出。
一、复审结论
上一轮报告中的 4 项确认问题均已完成代码级修复,未发现原问题仍可沿原路径复现:
| 原问题 | 复审结论 | 关键位置 |
|---|---|---|
P1-01 匿名 vote_fp Cookie 可伪造 |
已修复 | route/vote/vote.go:62-120, 691-700 |
| P1-02 Markdown 实体编码导致 XSS | 已修复 | internal/template/markdown.go:143-147 |
| P2-01 多选重复选项可重复计票 | 已修复 | route/vote/vote.go:232-247, 749-775, 802-809 |
| P2-02 投票次数配置与一人一票约束矛盾 | 已修复 | route/user/vote.go:199-202, 563-566、templates/user/vote-form.html:85-88 |
本轮复审另发现并修复了一个由 P1-01 修复引入的兼容性问题:签名 token 长度为 108 字符,原数据库字段 size:64 不足,已调整为 size:128(internal/db/vote_submissions.go:54-55)。
当前代码级结论:未发现 P0/P1/P2 确认问题。
二、逐项复审证据
1. P1-01:匿名指纹 Cookie 伪造与防刷绕过
状态:已修复。
newVoterToken生成 32 字节随机 nonce,格式为64 位十六进制 nonce + "." + HMAC-SHA256 签名。signVoterToken使用conf.Server.XSRFKey作为服务端密钥;配置初始化已强制要求该密钥非空。validVoterToken检查分段数量、nonce 长度、十六进制格式,并使用hmac.Equal比较签名。ensureVoterToken对无效/伪造 Cookie 重新签发;currentVoterToken对无效 Cookie 返回空值,不再将客户端任意字符串作为身份依据。- 匿名提交无论是否带有效 Cookie,均按投票维度执行 24 小时 IP 计数;当前一人一票策略下,同一投票同一 IP 窗口内最多接受一次匿名提交。
- 数据库
UNIQUE(vote_id, voter_token)仍作为有效 token 的最终并发防重兜底。
残余设计取舍:IP 兜底会对 NAT、校园网、公司网络等共享出口产生误伤;这是保守防刷策略,不是身份伪造漏洞。若业务必须支持共享出口多人投票,应改用验证码、设备/行为风控或更细粒度限流,而不能取消服务端限流。
2. P1-02:Markdown 实体编码 XSS
状态:已修复。
sanitizeHTML的html.TextToken分支现在对 tokenizer 已解码的文本调用html.EscapeString后再写入template.HTML。- 因此
<script>、双重编码输入不会在重建 HTML 时重新变成可执行标签。 - 标签、属性、URL 白名单逻辑保持有效;事件属性和危险协议仍被过滤。
- 回归测试已覆盖实体编码、双重编码、script、事件属性、危险 URL、iframe、SVG、style 以及 font color 属性注入。
3. P2-01:多选重复 option ID 重复计票
状态:已修复。
parseMultipleOptionIDs统一负责多选参数解析。- 解析时使用
seen集合;同一option_id出现两次直接返回“选项不能重复选择”。 - 同时校验空提交、最大选项数、选项归属和
uint溢出,避免异常数值绕过校验。 LoadResults的多选聚合层也对历史数据中的重复 option ID 做防御性去重,因此旧异常数据不会在每次结果计算中再次放大统计。- 新增测试覆盖正常多选、重复选项、超出上限、无效选项和整数溢出。
4. P2-02:MaxVotesPerFingerprint 与数据库约束矛盾
状态:已修复。
- 当前数据模型明确采用一人一票:登录用户由
UNIQUE(vote_id, user_id)约束,匿名用户由UNIQUE(vote_id, voter_token)与 IP 兜底共同保护。 - 管理端解析不再信任旧页面或手工请求提交的
max_votes_per_fingerprint,统一按1保存。 - 新建/编辑失败回填也固定显示
1;表单输入设置为readonly、max="1",避免继续传达可配置多票语义。 - 四种语言的字段文案已改为明确的一人一票含义。
三、本轮新增兼容性问题及修复
签名 token 的组成长度为:64 字符 nonce + 1 个分隔符 + 43 字符 Raw Base64URL HMAC = 108 字符。复审过程中确认原字段:
VoterToken *string `gorm:"size:64;..."`
不足以存储完整 token,可能导致 MySQL 严格模式下匿名提交失败或 token 截断。现已改为:
VoterToken *string `gorm:"size:128;uniqueIndex:idx_submission_token"`
并加入 token 长度回归断言。部署时依赖启动阶段的 GORM AutoMigrate 将字段扩容;上线前应在预生产数据库确认迁移结果和唯一索引状态。
四、验证结果
通过
gofmt -d route/vote/vote.go route/vote/vote_stats_test.go route/user/vote.go \
internal/db/vote_submissions.go
# 无输出,格式通过
go test -count=1 ./route/vote ./internal/template ./templates ./internal/i18n \
./internal/security/autoban ./internal/security/censor ./internal/imagecheck \
./internal/shared ./internal/cache
# 全部 PASS
go vet ./route/vote ./internal/template ./templates ./internal/i18n
# 无输出,检查通过
全量验证限制
go test ./...
go vet ./...
未能全绿,原因是当前环境已有的基础设施限制,不是本轮修复代码引入:
github.com/chai2010/[email protected]在当前 Windows 环境缺少 CGO/WebP 编译符号(如webpGetInfo、webpDecodeRGB),导致internal/storage、route/question、route/user、主程序等相关包无法完整编译。internal/db测试未配置数据库类型,测试初始化报Unknown database type: ""。
因此仍需在生产同构的 Linux/CGO 构建环境,并配置测试数据库后执行:
go test ./...
go vet ./...
govulncheck ./...
五、上线前建议
- 在预生产数据库执行/确认 AutoMigrate,确认
vote_submissions.voter_token已扩容到至少 128 字符,且唯一索引仍存在。 - 使用两个不同匿名客户端验证:同一 IP 同一投票第二次提交被拦截;伪造
vote_fp会被覆盖且不能绕过 IP 限制;正常匿名首次投票成功。 - 用真实浏览器验证实体编码文本仅以文字显示,不执行脚本;覆盖提问、回答、投票描述和结果页等 Markdown 输出点。
- 若站点存在大量共享出口,结合验证码或行为风控评估 IP 一票策略的误伤率。
六、修改文件清单
internal/template/markdown.gointernal/template/markdown_test.goroute/vote/vote.goroute/vote/vote_stats_test.goroute/user/vote.gotemplates/user/vote-form.htmlinternal/db/vote_submissions.gointernal/i18n/dict_zh_cn.gointernal/i18n/dict_zh_tw.gointernal/i18n/dict_en.gointernal/i18n/dict_ja.go