Files
FileShare/docs/security-audit-2026-09-05.md
T
SKYMirror 84df9996cb
CI 测试 / go vet + go test (push) Successful in 49s
26.9:版本号统一 + CI 精简 + 前端产物重建
- 全项目版本号统一:v3.x 迭代号(26.9/26.9/26.9/26.9 及裸 v2/v3)→ 26.9,
  覆盖 Go 注释 / 文档 / openapi.yaml / README×4 / 前端源码(80+ 处)
- v31_test.go 更名 custom_code_test.go;TestV2AccessorDefaults → TestKVAccessorDefaults
- docs/api/00-overview.md 更新日志合并为单条 26.9 条目(修复错位拼接)
- .goreleaser.yaml 头部注释与实际一致(Pro 2.18.1 / GITEA_TOKEN / semver tag 要求)
- CI:release-image.yml → ci.yml,仅保留 vet+test 门禁;
  镜像发布移交 GoReleaser Pro(原 build-push 的 tag 校验与 26.9 版本方案冲突,历史 9 次失败)
- 前端重建:server/web/dist 与 web-embed 同步(docs 文案嵌入更新)
2026-09-08 03:16:51 +08:00

19 KiB
Raw Blame History

FileCodeBox Go 重写版 · 安全审计报告

  • 审计日期:2026-09-05
  • 审计范围:server/Go 1.27.1 Gin+GORM 后端,56 个文件约 13,900 行)、web/Vue 3 前端)、deploy/Dockerfile / docker-compose
  • 审计方式:人工代码审读(认证/会话、上传下载全链路、存储引擎、配置与注入面)+ 工具佐证(go vetgovulnchecknpm audit
  • 结论速览:未发现可直接导致 RCE、SQL 注入、路径穿越或认证绕过的高危问题;发现 5 项中危问题(密码哈希强度、S3 直传校验缺口、请求体无上限 DoS、依赖漏洞、上传会话资源滥用)与若干低危/加固建议。
  • 修复状态(2026-09-05 第二轮):M1M5、L1L10 及可行动 Info 项已全部修复,逐项见各条目「 修复」标记;验证:gofmt/go vet/go test ./... 全绿,govulncheck 0 命中,二进制端到端冒烟(初始化→登录→审计落库→限流锁定→XSS 净化→robots→分享取件)通过。前端 markdown.ts 净化器加固需重建前端产物(已重建 web/distserver/web/dist)方可进入 go:embed 二进制。

一、中危(Medium

M1 管理员密码哈希强度不足(单轮 SHA256+盐) 已修复

  • 位置:server/internal/settings/password.go:15-21
  • 原状:HashPassword 生成 sha256$salt$hash,单轮 SHA256 + 16 字节随机盐。GPU 单卡对 SHA256 可达 10¹⁰ 次/秒,若数据库泄露(SQLite 文件/Postgres 备份),弱口令可被瞬间离线爆破。
  • 修复:HashPassword 改为 bcryptcost 12,输出 bcrypt$ + 原生 $2a$12$… 哈希串;VerifyPassword 兼容 bcrypt$/sha256$/明文三种历史格式实现平滑迁移;新增 NeedsRehash,管理员登录成功时自动将旧格式重哈希写回(adminLogin 内触发);密码长度 >72 字节按 bcrypt 语义截断(bcryptBytes);GenerateJWTSecret 的 rand 错误不再忽略(panic 显式失败)。测试:settings 包 + TestAdminPasswordAutoUpgrade

M2 S3 预签名直传(direct 模式)绕过大小与类型校验 已修复

  • 位置:server/internal/api/presign.go:59-171init)、presign.go:275-345confirm)、server/internal/storage/s3.go:538-554PresignPutURL
  • 原状:
    • init 只校验声明file_size;预签名 PUT URL 未签入 Content-Length 约束,客户端实际可 PUT 任意大小对象;
    • confirm 仅 FileExists,不 Stat 校验实际对象大小,分享记录 Size 直接取声明值;
    • magic bytes / 类型白名单校验在 direct 模式完全不生效(内容不经过服务器)。
  • 影响:开启游客上传 + S3 引擎的部署中,任意访客可绕过 max_file_sizestorageLimit(配额按声明值记账),向桶内塞入任意大小/内容的对象(存储成本攻击、策略绕过)。
  • 修复(多引擎一致,local/S3/WebDAV 全覆盖):
    1. Storage 接口新增 HeadMeta(ctx, savePath, headBytes)S3 用 Range GET bytes=0-(n-1)WebDAV 用 Stat + Rangelocal 直接 Open
    2. presignConfirm 现在:HeadMeta 取实际大小 → 超策略上限则 DeleteFile + 释放配额 + 403;实际大小与声明差 >1KB → DeleteFile + 400;前 64 字节补做 validateFileMagic
    3. presignInit 拒绝 file_size <= 0 声明;
    4. 测试:TestPresignConfirmRejectsOversizeObjectTestPresignConfirmRejectsSizeMismatch

M3 请求体无全局大小上限,多处“先整读后校验”可被 DoS 已修复

  • 位置:
    • server/internal/api/helpers.go:681-721bindJSONOrForm 对 text/plain 形态 io.ReadAll 全量进内存;JSON 绑定同样全量读入)
    • server/internal/api/chunk.go:314io.ReadAll(LimitReader(f, ChunkSize+1)) 单分片全量进内存,chunk_size 上界=策略 max_file_sizeschema 允许至 10GiB
    • server/internal/api/share.go:100-128(文本 222KB 限制在读完整个 body 之后才判定)
  • 原状:全服务无 http.MaxBytesReader,也无上传前的 Content-Length 预检;multipart 大文件会先被完整解析(>32MB 落临时盘)后才被 CheckSize 拒绝。
  • 影响:默认 openUpload=1 的部署下,未认证攻击者可用大 body 消耗内存/磁盘/带宽。
  • 修复:
    1. 新增 middleware.BodyLimit(limitFn)main.go 全局装配(在 GuardNotInitialized 之后、Audit 之前):/setup/admin/*/share/text|metadata|select 一律 1MiB,其余端点 maxFileSize(+2MiB 开销)(前端单请求单分片,已核实安全);
    2. /share/text 入口先查 Content-Length > 441KB 直接 403(读前预检);
    3. 单分片 chunk_size 硬上限 32MiB(超出 400),不再跟随 10GiB 的文件策略;
    4. 测试:TestChunkSizeCap

M4 依赖漏洞(govulncheck 实际命中 3 个 + 8 个 imported 级) 已修复

  • 工具输出(govulncheck ./...):
    • golang.org/x/text v0.30.0GO-2026-5970 非法输入死循环 DoS(经 gorm 归一化路径可达),修复于 v0.39.0
    • github.com/quic-go/quic-go v0.54.0GO-2026-5676(修复 v0.59.1)、GO-2025-4233(修复 v0.57.0QPACK 扩张 DoS(实际未启用 HTTP/3 监听,实践影响低);
    • golang.org/x/net v0.45.08 个 imported 级漏洞(GO-2026-5030/5029/5028/5027/5026/5025/4918 等,含 HTTP/2 传输死循环),修复于 v0.53v0.55。
  • 修复:x/text v0.41.0x/net v0.58.0x/crypto v0.56.0(转为直接依赖,供 bcrypt)、quic-go v0.59.1govulncheck ./... 复扫 0 个可达漏洞(模块级仅剩 1 个未调用项)。前端 npm audit 仍报 2 个 moderatevue-i18n → @intlify/core-base,上游暂无修复版,保持关注升级)。

M5 上传会话与容量预留可被滥用(init 不计数、无过期清理) 已修复

  • 位置:server/internal/api/chunk.go:39-157chunkInit 从不 Limiter.Add,上传限流仅在 complete/presign-init/shareFile 成功时计数)、server/internal/api/helpers.go:380-436(预留 TTLchunk 24h / presign 15min
  • 原状:
    • 游客可无限次 POST /chunk/upload/init 创建会话(每次写入 upload_chunks 行 + 24h 容量预留),无后台任务回收过期预留、未完成会话与孤儿分片对象(local chunks/ 目录、S3 *.part);
    • 若配置了 storageLimit,攻击者可用多次 init 把全部配额占用满 24 小时 → 全站上传 507(拒绝服务);默认 storageLimit=0 时则是磁盘/DB 垃圾持续累积。
  • 修复:
    1. chunkInit 在新会话保留成功后即 Limiter.Add(c, LimitUpload) 计数;
    2. chunk 预留 TTL 24h → 2h(续传刷新);
    3. 新增 internal/janitor 后台清理循环(默认 10 分钟):过期 storage_reservations、超时(>24h 未完成)upload_chunks 会话(连带清理分片对象)、过期直传 presign 会话(连带删除残留对象);main.go 启动时随 ctx 拉起。

二、低危(Low

L1 初始化向导(/setup)存在接管窗口 已修复

  • 位置:server/internal/api/setup.go:47-63middleware/audit.go:222-236
  • 原状:服务公开到公网后、管理员完成 /setup 前,任何人可抢先完成初始化并设置管理员密码(经典 setup race;双检查只防并发写坏,不防抢占)。
  • 修复:新增 autoInitIfNeededmain.goSystemStart 后执行)——设置 FCB_ADMIN_PASSWORD(≥8 位,否则告警跳过)即可在服务启动瞬间完成管理员初始化并生成 jwt_secret,消除 /setup 被抢占窗口;deploy/.env.example 与 README 安全清单已补充说明(初始化后建议移除该变量)。

L2 下载令牌为非 HMAC 拼接哈希且非常量时间比较 已修复

  • 位置:server/internal/api/helpers.go:249-253sha256(code‖timeFactor‖"000"‖secret))、share.go:458key != GetSelectToken(...)
  • 原状:secret 后置拼接,长度扩展不适用、256 位密钥不可爆破,当前不可实际利用;但拼接串存在理论歧义(code 与时间窗数字边界重叠),且字符串比较非常量时间。
  • 修复:GetSelectToken 改为 HMAC-SHA256(secret, code‖timeFactor);新增 VerifySelectTokenhmac.Equal 常量时间比较(允许当前/上一两个时间窗,防临界失效);shareDownload 已切换到 VerifySelectToken

L3 数字取件码空间过小,防撞库完全依赖单 IP 限流 已修复

  • 位置:server/internal/api/helpers.go:114-125validatePickupCode4 位下限)
  • 原状:code_generate_type=number 时仅 9 万空间(5 位数字),默认 errorCount=10/分/IP 下单 IP 需约 6 天扫完,分布式多 IP 可显著缩短;自定义码允许 4 位(36⁴≈168 万)。
  • 修复:自定义提码最小长度 4 → 5 位pickupCodeMinLen=536⁵≈6000 万空间);测试 custom_code_test.goTestPickupCodeMinLen 同步更新。

L4 enableChunk 开关后端不强制 已修复

  • 位置:server/internal/api/router.go:56-67
  • 原状:/chunk/* 路由不检查 cfg.EnableChunk(),关闭开关后接口仍可用(仅前端隐藏入口)。若该开关被当作安全策略,需在 handler 层强制(403)。
  • 修复:新增 requireChunkEnabled 守卫(关闭时 403「分片上传未启用」),挂到 chunkInit(首检查)、chunkUploadchunkComplete 三个端点;测试 TestChunkToggleEnforced 验证 0→403 / 1→200。

L5 管理端安全事件未入审计日志 已修复

  • 位置:server/internal/middleware/audit.go:55-78DefaultClassifier 仅覆盖 upload/download
  • 原状:管理员登录失败、配置修改、密码修改、引擎切换、文件删除等管理操作均不落审计。
  • 修复:DefaultClassifier 扩展 adminAuditActionslogin/logout、config/update、settings/password、storage/switch、file/update/delete/batch-*、policy-action 等 POST/PATCH/DELETE 敏感操作 → audit.ActionAdmin);Audit 中间件跳过条件由「非 upload/download 即跳过」改为「分类未命中才跳过」,admin 动作同样建 auditEntry 并按 HTTP 状态兜底落库;新增 audit_l5_test.go 回归。端到端实测:登录失败 401 落 admin/denied、成功落 admin/success

L6 CORS 对所有接口(含 /admin/*)放开 * 已修复

  • 位置:server/internal/middleware/cors.go:10
  • 原状:Bearer 模式下无 CSRF 风险,但一旦 token 泄露(见 L8),任意网站均可跨域携带 token 调用管理 API。
  • 修复:Cors 重写——/admin/* 请求带 Origin 且既不同源也不在白名单(site_domain 配置注入)时不回任何 CORS 头(浏览器拦截跨域读取),预检直接 204;公开接口维持 *Bearer 认证,无 Cookie CSRF 面);无 Origin 的非浏览器请求不受影响。

L7 管理端通知内容 v-html 直出(存储型 XSS 面) 已修复

  • 位置:web/src/components/NotifyPop.vue:23notify_content 来自 GET /api/v1/config,管理端可设)
  • 原状:设计上"允许 <a> 等受控 HTML",但服务端/前端均无净化。管理员账号被盗即可对全站访客注入脚本。
  • 修复(双侧):
    1. 服务端新增 settings.SanitizeInlineHTML 白名单净化器(sanitize.go + 18 个单测):仅保留文本与 <a href="http(s)://|/|#">(引号内 >、未闭合标签、script/style/iframe/svg/math 等危险标签连内容整体丢弃、事件属性不透传、javascript:/data: 拒绝);在 adminConfigUpdate 写入侧publicConfig 读取侧 双重调用(覆盖历史存量与直改库数据);
    2. 前端 web/src/utils/markdown.ts 净化器加固:黑名单补 svg/math/frame/applet/template/noscript 等,新增 srcdoc/sandbox/formaction/action/xlink:href/srcset 等危险属性表,URL 校验改为协议白名单(http/mailto/相对/锚点,src 另许 data:image/)。端到端实测:<script>alert(1)</script>javascript: 链接被剔除、合法 <a href="https://…"> 保留。
    • 注:前端净化器改动需重建前端产物(已重建 web/dist 并同步 server/web/dist)方进入 go:embed 二进制;未来渲染任何外部内容前建议仍替换为 DOMPurify。

L8 管理员令牌存 localStorage、默认会话 30 天 已修复

  • 位置:web/src/api/http.ts:14-33server/internal/config/config.go:110
  • 原状:XSS 可窃取且有效期长(可配 1–365 天)。
  • 修复:AdminSessionExpireDefault 30 天 → 7 天(仍可配 1–365 天,config 包测试通过);敏感操作改密已有旧密码校验 + jwt_secret 轮换(全部旧 token 失效)。FCB_ADMIN_SESSION_EXPIRE 环境变量种子同步支持(main.go + .env.example 注释)。

L9 缓存故障时限流 fail-open 已修复

  • 位置:server/internal/middleware/ratelimit.go:148-164
  • 原状:Redis/缓存异常时 Check 一律放行(登录爆破防护随之失效)。
  • 修复:Check/Add 区分 cache.ErrNotFound(窗口内无计数,正常放行)与真实缓存故障;故障时降级为进程内固定窗口计数(带过期清理与容量上限 8192,单实例语义),缓存恢复自动回到共享缓存;测试 TestRateLimiterFallbackOnCacheFailure 证明故障降级下超限后 fail-close 拒绝。端到端实测:连续错误密码 3 次 401 后第 4 次起 423 锁定。

L10 反向代理场景的限流与审计 IP 失真 已修复(文档 + 配置面)

  • 位置:server/internal/middleware/ratelimit.go:32-96config.go:52
  • 原状:实现(仅信任 FCB_TRUSTED_PROXIES 命中的直连地址才采信 XFF)是正确的;但部署文档未强调反代后必须配置可信代理,否则全体用户共享代理 IP 的限流桶(互相误伤)且审计 IP 全是代理地址;反向配置错误则可伪造 XFF 绕过限流。
  • 修复:deploy/README.md 新增「安全清单(生产部署必读)」7 条(可信代理、立即初始化/FCB_ADMIN_PASSWORD、修改组件默认凭据与端口发布、Postgres TLS、会话有效期、限流降级语义、内置清理任务);deploy/.env.exampleFCB_TRUSTED_PROXIESFCB_ADMIN_PASSWORDFCB_ADMIN_SESSION_EXPIRE 补充注释说明,弱凭据处(MinIO/WebDAV/Postgres)加「仅限本机冒烟,生产必须修改」警示。

三、提示 / 信息(Info)修复情况

  1. version/storage_engine 公开暴露(小信息收集面):保留(前端展示与诊断需要,风险极低)。
  2. compose 弱凭据/端口发布: 已在 .env.example 与 README 安全清单加警示(凭据为示例值,端口建议删除或绑 127.0.0.1)。
  3. robotsText 无路由: 已新增 GET /robots.txtrouter.go,输出配置内容,text/plain);端到端实测 200 且内容生效。
  4. adminFileList LIKE 未转义: 已加 escapeLike\\\%\%_\_+ ESCAPE '\'admin-only,防御性修复)。
  5. Postgres 并发配额超额记账: reserveStorage 包事务并对 Postgres 加 pg_advisory_xact_lock"FCBQ" 键)串行化配额判定;SQLite 写串行不受影响。
  6. crypto/rand 错误忽略: GenerateJWTSecret 已改 panic 显式失败(见 M1);generateCode/randomHex 维持重试回退(非安全关键路径)。

四、确认到位的安全设计(无需修改)

  • JWTHS256 + WithValidMethods 双重防算法混淆;密钥为 64 位 hex 随机;改密自动轮换(使全部旧会话失效);过期/签名校验完备。
  • SQL:全部 GORM/占位符参数化;排序字段白名单(normalizeSortBy);无字符串拼接 SQL。
  • 路径安全SanitizePath/SanitizeFileName + withinRoot + EvalSymlinks 符号链接逃逸双重校验;S3 key、WebDAV 路径同样清洗;未发现穿越。
  • 下载响应:统一 application/octet-stream + attachment + RFC 5987 文件名编码,杜绝分享文件被当 HTML 渲染的存储型 XSS;取件文本以 text/plain 下发且前端 <pre>{{ }}</pre> 渲染。
  • 认证/授权/admin/* 全组 Bearer 鉴权;/setup 受 GuardNotInitialized 白名单约束;旧默认密码 FileCodeBox2023 视为未初始化;verifyLegacyDefault/密码比较用 hmac.Equal
  • 上传策略:大小上限五处口径一致(share/chunk-init/chunk-累计/presign/complete),magic bytes 防伪造,自定义码查重 + 唯一索引兜底并发。
  • 限流:取件错误/登录失败/上传/metadata 四类固定窗口;clientIP 仅信任显式配置的代理。
  • 容量:单条 INSERT..SELECT 原子预留判定,防超卖。
  • 部署:多阶段构建、非 root(uid 10001)运行、.dockerignore 合理、.env 未含真实密钥(当前也非 git 仓库;初始化 git 后应将 deploy/.env 加入忽略清单)。

五、修复优先级建议(原计划,已全部落实)

优先级 事项 状态
立即 M4 依赖升级;M2 的 confirm 大小校验 已完成
短期 M1 密码哈希迁移 bcryptM3 全局 MaxBytesReaderM5 init 计数 + 清理循环 已完成
计划 L1L10 按运营形态取舍 已全部修复

六、修复验证记录(2026-09-05 第二轮)

  • 静态检查gofmt -l . 无输出、go vet ./... 通过、go build ./... 通过。
  • 单元/集成测试go test ./... 全绿(api/audit/cache/config/database/middleware/settings/storage 全部 ok),新增修复回归测试:
    • settings/sanitize_test.goL7 净化器 19 例)
    • middleware/audit_l5_test.goL5 admin 审计落库)
    • middleware/ratelimit_fallback_test.goL9 缓存故障降级 fail-close
    • api/security_fixes_test.goL4 开关强制、M3 chunk 上限、L3 提码长度、M2 confirm 大小/超限、M1 密码迁移)
  • 依赖扫描govulncheck ./... 0 可达漏洞;npm audit --omit=dev 剩 2 moderatevue-i18n 上游未发布修复,跟踪中)。
  • 端到端冒烟(编译二进制 + SQLite 实跑):
    1. /setup 初始化 200(M1 生效:DB 中 admin_token = bcrypt$$2a$12$…);
    2. 错误密码登录 401 → 审计落 admin/denied;成功登录落 admin/successL5);
    3. 连续 3 次错误后第 4 次起 423 锁定(L9 限流);
    4. 配置写入 <script>alert(1)</script> + javascript: 链接 → /api/v1/config 输出已剔除、合法 <a href> 保留(L7);
    5. /robots.txt 200 且内容生效(Info3);
    6. 文本分享创建 + 取件下载全链路 200。
  • 遗留跟踪vue-i18n 上游修复版本发布后升级(当前 npm audit 的 2 个 moderate 均来源于此)。