Files
go_blog/SECURITY_TODO.md
T
kevin 38cd09f723 fix: 安全加固,修复 P0/P1 安全漏洞
P0(高危):
- 新增全局 CSRF 中间件(同步器令牌),覆盖全部 30 个表单与 AJAX 请求
- 修复附件上传/列表/删除越权(IDOR),增加 admin/上传者/文章作者所有权校验
- 登录/注册成功后会话轮换,修复会话固定
- 会话密钥改用 crypto/rand 生成,配置缺失 secret 时拒绝启动

P1(中危):
- session 与 comment_uid cookie 增加 Secure/SameSite 标志
- 新增安全响应头:CSP、X-Content-Type-Options、X-Frame-Options、HSTS 等
- 新增 web.trusted_proxies 配置,修复 X-Forwarded-For 伪造
- 修复浏览量记录 goroutine 访问已回收 gin.Context 的数据竞争

补充 17 个安全回归测试(middleware/handlers),go test -race 全绿
2026-08-19 12:33:09 +08:00

8.5 KiB
Raw Blame History

安全修复 TODO

基于 2026-08-19 的安全审计(源码 + haibara.ai 线上验证)整理。 按优先级排序,完成后勾选并标注日期。


P0 — 立即修复

[x] 1. 会话密钥弱回退(可伪造管理员会话) 2026-08-19

  • 位置: config/config.gogenerateSecret / applyDefaults 回退)
  • 问题: secret 缺失时回退为 SHA-256(主机名+PID),两者均可被外部推测/爆破,攻击者可离线伪造任意用户会话 cookie。
  • 修复:
    • generateSecret() 改用 crypto/rand 生成 32 字节随机数
    • 已有配置加载路径中 secret 为空时:拒绝启动(log.Fatalf),不再静默回退;配置文件读取失败也改为直接退出
    • 首次生成配置文件时写入强随机 secret(保持 install_linux.sh 的 openssl 路径不变)
  • 验证: 新密钥为 crypto/rand 输出;缺失 secret 时启动直接报错

[x] 2. 全站无 CSRF 防护 2026-08-19

  • 位置: 全部 POST 路由(登录/注册/文章/评论/管理后台/设置/附件)
  • 问题: 仅靠 cookie 认证,无 CSRF token;线上 cookie 无 SameSite 属性,浏览器默认 Lax 保护不完整(Chrome Lax+POST 豁免、Safari 差异)。
  • 修复:
    • 新增 middleware/csrf.go:同步器令牌模式(session 存储、常量时间比较),表单 _csrf 字段或 X-CSRF-Token 头二选一,不匹配返回 403
    • 覆盖全部 30 个 POST 表单(含游客评论表单);AJAX(附件上传/删除、头像上传)经 <meta name="csrf-token"> 下发 token 并以请求头携带
    • /article/:slug/comments 游客 POST 一并覆盖(游客同样有 session)
  • 验证: middleware/csrf_test.go 7 用例 + 端到端 curl 冒烟(无 token/伪造 token 403,有效 token 302

[x] 3. 附件接口越权(IDOR 2026-08-19

  • 位置: handlers/attachment.goDeleteAttachment / ListAttachments / UploadAttachment 的 article_id
  • 问题: /my/articles/attachments/* 仅要求登录,无所有权校验;任意登录用户可删除/列出全站任意附件、向他人文章挂附件。
  • 修复:
    • DeleteAttachment:admin / 上传者 / 所属文章作者三者之一,否则 403
    • ListAttachments:文章作者或 admin,否则 403
    • UploadAttachmentarticle_id != 0 时校验文章归属(admin 除外),否则 403
    • 单元测试(handlers/security_test.go:越权 403 / 本人 200 / admin 覆盖)
  • 验证: 普通用户 A 删除用户 B 的附件 -> 403(测试覆盖)

[x] 4. 会话固定(Session Fixation 2026-08-19

  • 位置: handlers/auth.goLogin / Register 自动登录)
  • 问题: 登录成功后未清空旧 session,直接写入 user_id,固定攻击可劫持登录后会话。
  • 修复:
    • 认证成功后先 session.Clear() 再写入 user_id/username 并 Save;保留 lang 与 csrf_token(避免多标签页已渲染表单失效)
  • 验证: 登录前后 cookie 值不同,旧 cookie 无法访问受保护路由(TestLoginRotatesSession

P1 — 近期修复

  • 位置: main.gosession store)、handlers/comment.go:82comment_uid
  • 修复:
    • store 默认 SameSite: LaxSecure 按请求动态设置(middleware/https.go 检测 TLS 或 X-Forwarded-Proto),通过中间件在每次请求时应用到 session cookie
    • comment_uid 游客 cookie 同步补齐 SameSite=Lax + HTTPS 下 Secure
  • 验证: 模拟 HTTPS 请求响应头 Set-Cookie: ... HttpOnly; Secure; SameSite=Lax;冒烟测试通过

[x] 6. 缺失安全响应头 2026-08-19

  • 位置: 新增 middleware/security_headers.go(全局第一个注册)
  • 修复:
    • Content-Security-Policydefault-src 'self' + 现有 CDN 白名单 + frame-ancestors 'none' 等)
    • X-Content-Type-Options: nosniffX-Frame-Options: DENYReferrer-PolicyPermissions-Policy
    • Strict-Transport-Security(仅 HTTPS 请求下发,未加 includeSubDomains 以免影响 HTTP 子域)
  • 说明: CSP 含 'unsafe-inline'(模板内联 script/style 必需);待 P2-9 CDN 本地化后可进一步收紧
  • 验证: middleware/security_headers_test.goheaders 存在性、HSTS 条件下发)+ 冒烟 curl 确认

[x] 7. X-Forwarded-For 伪造(IP 审计/浏览量可刷) 2026-08-19

  • 位置: handlers/helpers.goGetClientIP)、config/config.goWebConfig.TrustedProxies)、main.go
  • 修复:
    • 删除手动解析 XFF 首值逻辑,GetClientIP 改为 c.ClientIP()
    • router.SetTrustedProxies(cfg.Web.TrustedProxies);新增 web.trusted_proxies 配置项(默认 ["127.0.0.1", "::1"]unix socket 部署自动信任)
    • gin 内部 XFF 从右往左取第一个不可信 IP:直接客户端伪造的 XFF 被忽略
  • 验证: middleware/clientip_test.go(直接连接带假 XFF 取真实 IP / 代理链取最右不可信条目)

[x] 8. goroutine 数据竞争(use-after-return 2026-08-19

  • 位置: handlers/home.goArticleDetail → recordArticleView
  • 修复:
    • goroutine 启动前同步提取 userID / ip / UA 为局部变量,recordArticleView 不再触碰 gin.Context 与 session
  • 验证: go test -race ./... 全绿

P2 — 计划修复

[ ] 9. 第三方 CDN 无 SRI / Tailwind dev CDN

  • 位置: templates/layouts/base.html:16-20:118-122
  • 修复:
    • 将 marked / DOMPurify / highlight.js / cropperjs / easymde 下载到 static/vendor/,走 go:embed 本地分发(静态管线已具备)
    • 替换 cdn.tailwindcss.com 为构建期生成的静态 CSS(或至少加 SRI)
    • 本地化后配合 #6 收紧 CSP 为 default-src 'self'
  • 验证: 断网第三方域名后页面渲染功能完整;CSP 无违规报告

[ ] 10. 登录无速率限制

  • 位置: handlers/auth.go:33
  • 修复:
    • 按 IP + 用户名维度做失败计数(内存或 DB),如 5 次失败锁定 15 分钟
    • 失败提示保持统一(现有 ?error=1 已做用户名枚举防护,保持)
  • 验证: 连续错误登录后返回锁定提示

[ ] 11. 配置文件权限过宽

  • 位置: config/config.go:115
  • 修复: os.WriteFile(configFile, data, 0640)secret 写入后可选 os.Chmod

[ ] 12. 首启弱凭据 admin/admin

  • 位置: models/db.go:58-81
  • 修复:
    • 方案 A:首启生成随机密码打印一次性提示
    • 方案 B:admin 账户标记"必须改密",登录后强制跳转改密页
  • 说明: 线上已改密(已验证),此项为防御新部署

[ ] 13. Unix socket 权限 666

  • 位置: install_linux.sh:80
  • 修复: chmod 660 + chown root:blog_go(反向代理进程加入同组),避免本机任意用户绕过 Cloudflare 直连

P3 — 低优先级 / 观察项

[ ] 14. 上传不校验文件真实类型

  • 位置: handlers/upload_validator.go:33-58
  • 修复: 用 github.com/gabriel-vasile/mimetype(已在依赖树中)校验 magic bytes 与扩展名/MIME 一致;不一致则拒绝
  • 说明: 白名单无 .svg/.html,存储型 XSS 风险低,主要是恶意文件托管风险

[ ] 15. Gravatar MD5 邮箱哈希可反查

  • 位置: handlers/comment.go:88-91
  • 说明: Gravatar 协议本身如此;若在意隐私可加后台开关(已有 UseGravatar 开关可关闭)

[ ] 16. RSS 以 Host 头构造 baseURL

  • 位置: handlers/rss.go:60-64
  • 修复: 从站点设置中读取固定站点 URL,仅在与请求 Host 不符时告警
  • 说明: Cloudflare 会校验 Host,实际可利用性低

[ ] 17. bcrypt cost 偏低

  • 位置: models/user.go:41DefaultCost=10
  • 修复: 提升到 12;已有哈希在用户下次改密时自然升级

不需要修复(已确认安全)

  • SQL 注入:全参数化查询(GORM
  • XSShtml/template 自动转义 + 评论双防御(服务端 strip + DOMPurify
  • 密码哈希:bcrypt
  • 附件路径穿越:SHA-256 内容寻址文件名
  • 线上默认凭据:已修改(已验证)
  • 注册接口:已关闭(已验证)

建议执行顺序

  1. #1 → #4 → #5(一次提交:会话安全三件套,改动小、风险低)
  2. #3(附件越权,纯 handler 层校验)
  3. #2(CSRF,涉及全站表单,改动面最大,单独一个 PR 充分回归)
  4. #7 → #8 → #6IP/竞态/响应头)
  5. P2/P3 按迭代排入