Go 1.27.1 (Gin+GORM) + Vue 3 文件快传服务: - 安全审计全部修复(docs/security-audit-2026-09-05.md): bcrypt 密码哈希与自动升级、presign 直传服务端大小/内容校验、 全局请求体上限、依赖升级(govulncheck 0 命中)、janitor 后台清理、 管理端审计动作落库、/admin CORS 收紧、通知内容白名单净化、 会话默认 7 天、限流缓存故障降级、robots.txt 端点等 - 前端:取件链接复制修复(不再重复拼接提取码)、markdown 净化器加固 - Redis 支持库号(FCB_REDIS_DB / redis://…/db URL) - 文档:docs/api/* 与 openapi.yaml 同步最新行为(robots.txt、 提码 5 位起、chunk 32MiB 上限、admin 审计动作等) 验证:gofmt/go vet/go test 全绿;二进制端到端冒烟通过
19 KiB
19 KiB
FileCodeBox Go 重写版 · 安全审计报告
- 审计日期:2026-09-05
- 审计范围:
server/(Go 1.27.1 Gin+GORM 后端,56 个文件约 13,900 行)、web/(Vue 3 前端)、deploy/(Dockerfile / docker-compose) - 审计方式:人工代码审读(认证/会话、上传下载全链路、存储引擎、配置与注入面)+ 工具佐证(
go vet、govulncheck、npm audit) - 结论速览:未发现可直接导致 RCE、SQL 注入、路径穿越或认证绕过的高危问题;发现 5 项中危问题(密码哈希强度、S3 直传校验缺口、请求体无上限 DoS、依赖漏洞、上传会话资源滥用)与若干低危/加固建议。
- 修复状态(2026-09-05 第二轮):M1–M5、L1–L10 及可行动 Info 项已全部修复,逐项见各条目「✅ 修复」标记;验证:
gofmt/go vet/go test ./...全绿,govulncheck0 命中,二进制端到端冒烟(初始化→登录→审计落库→限流锁定→XSS 净化→robots→分享取件)通过。前端markdown.ts净化器加固需重建前端产物(已重建web/dist与server/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改为 bcrypt(cost 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-171(init)、presign.go:275-345(confirm)、server/internal/storage/s3.go:538-554(PresignPutURL) - 原状:
- init 只校验声明的
file_size;预签名 PUT URL 未签入 Content-Length 约束,客户端实际可 PUT 任意大小对象; - confirm 仅
FileExists,不Stat校验实际对象大小,分享记录Size直接取声明值; - magic bytes / 类型白名单校验在 direct 模式完全不生效(内容不经过服务器)。
- init 只校验声明的
- 影响:开启游客上传 + S3 引擎的部署中,任意访客可绕过
max_file_size与storageLimit(配额按声明值记账),向桶内塞入任意大小/内容的对象(存储成本攻击、策略绕过)。 - ✅ 修复(多引擎一致,local/S3/WebDAV 全覆盖):
Storage接口新增HeadMeta(ctx, savePath, headBytes);S3 用 Range GETbytes=0-(n-1),WebDAV 用 Stat + Range,local 直接 Open;presignConfirm现在:HeadMeta 取实际大小 → 超策略上限则 DeleteFile + 释放配额 + 403;实际大小与声明差 >1KB → DeleteFile + 400;前 64 字节补做validateFileMagic;presignInit拒绝file_size <= 0声明;- 测试:
TestPresignConfirmRejectsOversizeObject、TestPresignConfirmRejectsSizeMismatch。
M3 请求体无全局大小上限,多处“先整读后校验”可被 DoS ✅ 已修复
- 位置:
server/internal/api/helpers.go:681-721(bindJSONOrForm对 text/plain 形态io.ReadAll全量进内存;JSON 绑定同样全量读入)server/internal/api/chunk.go:314(io.ReadAll(LimitReader(f, ChunkSize+1))单分片全量进内存,chunk_size上界=策略max_file_size,schema 允许至 10GiB)server/internal/api/share.go:100-128(文本 222KB 限制在读完整个 body 之后才判定)
- 原状:全服务无
http.MaxBytesReader,也无上传前的 Content-Length 预检;multipart 大文件会先被完整解析(>32MB 落临时盘)后才被CheckSize拒绝。 - 影响:默认
openUpload=1的部署下,未认证攻击者可用大 body 消耗内存/磁盘/带宽。 - ✅ 修复:
- 新增
middleware.BodyLimit(limitFn),main.go全局装配(在 GuardNotInitialized 之后、Audit 之前):/setup、/admin/*、/share/text|metadata|select一律 1MiB,其余端点maxFileSize(+2MiB 开销)(前端单请求单分片,已核实安全); /share/text入口先查Content-Length > 441KB直接 403(读前预检);- 单分片
chunk_size硬上限 32MiB(超出 400),不再跟随 10GiB 的文件策略; - 测试:
TestChunkSizeCap。
- 新增
M4 依赖漏洞(govulncheck 实际命中 3 个 + 8 个 imported 级)✅ 已修复
- 工具输出(
govulncheck ./...):golang.org/x/text v0.30.0:GO-2026-5970 非法输入死循环 DoS(经 gorm 归一化路径可达),修复于 v0.39.0;github.com/quic-go/quic-go v0.54.0:GO-2026-5676(修复 v0.59.1)、GO-2025-4233(修复 v0.57.0)QPACK 扩张 DoS(实际未启用 HTTP/3 监听,实践影响低);golang.org/x/net v0.45.0:8 个 imported 级漏洞(GO-2026-5030/5029/5028/5027/5026/5025/4918 等,含 HTTP/2 传输死循环),修复于 v0.53–v0.55。
- ✅ 修复:
x/text v0.41.0、x/net v0.58.0、x/crypto v0.56.0(转为直接依赖,供 bcrypt)、quic-go v0.59.1;govulncheck ./...复扫 0 个可达漏洞(模块级仅剩 1 个未调用项)。前端npm audit仍报 2 个 moderate(vue-i18n → @intlify/core-base,上游暂无修复版,保持关注升级)。
M5 上传会话与容量预留可被滥用(init 不计数、无过期清理)✅ 已修复
- 位置:
server/internal/api/chunk.go:39-157(chunkInit 从不Limiter.Add,上传限流仅在 complete/presign-init/shareFile 成功时计数)、server/internal/api/helpers.go:380-436(预留 TTL:chunk 24h / presign 15min) - 原状:
- 游客可无限次
POST /chunk/upload/init创建会话(每次写入upload_chunks行 + 24h 容量预留),无后台任务回收过期预留、未完成会话与孤儿分片对象(localchunks/目录、S3*.part); - 若配置了
storageLimit,攻击者可用多次 init 把全部配额占用满 24 小时 → 全站上传 507(拒绝服务);默认storageLimit=0时则是磁盘/DB 垃圾持续累积。
- 游客可无限次
- ✅ 修复:
chunkInit在新会话保留成功后即Limiter.Add(c, LimitUpload)计数;- chunk 预留 TTL 24h → 2h(续传刷新);
- 新增
internal/janitor后台清理循环(默认 10 分钟):过期storage_reservations、超时(>24h 未完成)upload_chunks会话(连带清理分片对象)、过期直传 presign 会话(连带删除残留对象);main.go启动时随 ctx 拉起。
二、低危(Low)
L1 初始化向导(/setup)存在接管窗口 ✅ 已修复
- 位置:
server/internal/api/setup.go:47-63、middleware/audit.go:222-236 - 原状:服务公开到公网后、管理员完成 /setup 前,任何人可抢先完成初始化并设置管理员密码(经典 setup race;双检查只防并发写坏,不防抢占)。
- ✅ 修复:新增
autoInitIfNeeded(main.go,SystemStart 后执行)——设置FCB_ADMIN_PASSWORD(≥8 位,否则告警跳过)即可在服务启动瞬间完成管理员初始化并生成 jwt_secret,消除 /setup 被抢占窗口;deploy/.env.example与 README 安全清单已补充说明(初始化后建议移除该变量)。
L2 下载令牌为非 HMAC 拼接哈希且非常量时间比较 ✅ 已修复
- 位置:
server/internal/api/helpers.go:249-253(sha256(code‖timeFactor‖"000"‖secret))、share.go:458(key != GetSelectToken(...)) - 原状:secret 后置拼接,长度扩展不适用、256 位密钥不可爆破,当前不可实际利用;但拼接串存在理论歧义(code 与时间窗数字边界重叠),且字符串比较非常量时间。
- ✅ 修复:
GetSelectToken改为 HMAC-SHA256(secret, code‖timeFactor);新增VerifySelectToken用hmac.Equal常量时间比较(允许当前/上一两个时间窗,防临界失效);shareDownload已切换到VerifySelectToken。
L3 数字取件码空间过小,防撞库完全依赖单 IP 限流 ✅ 已修复
- 位置:
server/internal/api/helpers.go:114-125、validatePickupCode(4 位下限) - 原状:
code_generate_type=number时仅 9 万空间(5 位数字),默认errorCount=10/分/IP下单 IP 需约 6 天扫完,分布式多 IP 可显著缩短;自定义码允许 4 位(36⁴≈168 万)。 - ✅ 修复:自定义提码最小长度 4 → 5 位(
pickupCodeMinLen=5,36⁵≈6000 万空间);测试v31_test.go与TestPickupCodeMinLen同步更新。
L4 enableChunk 开关后端不强制 ✅ 已修复
- 位置:
server/internal/api/router.go:56-67 - 原状:
/chunk/*路由不检查cfg.EnableChunk(),关闭开关后接口仍可用(仅前端隐藏入口)。若该开关被当作安全策略,需在 handler 层强制(403)。 - ✅ 修复:新增
requireChunkEnabled守卫(关闭时 403「分片上传未启用」),挂到chunkInit(首检查)、chunkUpload、chunkComplete三个端点;测试TestChunkToggleEnforced验证 0→403 / 1→200。
L5 管理端安全事件未入审计日志 ✅ 已修复
- 位置:
server/internal/middleware/audit.go:55-78(DefaultClassifier 仅覆盖 upload/download) - 原状:管理员登录失败、配置修改、密码修改、引擎切换、文件删除等管理操作均不落审计。
- ✅ 修复:
DefaultClassifier扩展adminAuditActions(login/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:23(notify_content来自GET /api/v1/config,管理端可设) - 原状:设计上"允许
<a>等受控 HTML",但服务端/前端均无净化。管理员账号被盗即可对全站访客注入脚本。 - ✅ 修复(双侧):
- 服务端新增
settings.SanitizeInlineHTML白名单净化器(sanitize.go+ 18 个单测):仅保留文本与<a href="http(s)://|/|#">(引号内>、未闭合标签、script/style/iframe/svg/math等危险标签连内容整体丢弃、事件属性不透传、javascript:/data:拒绝);在 adminConfigUpdate 写入侧 与 publicConfig 读取侧 双重调用(覆盖历史存量与直改库数据); - 前端
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-33、server/internal/config/config.go:110 - 原状:XSS 可窃取且有效期长(可配 1–365 天)。
- ✅ 修复:
AdminSessionExpireDefault30 天 → 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-96、config.go:52 - 原状:实现(仅信任
FCB_TRUSTED_PROXIES命中的直连地址才采信 XFF)是正确的;但部署文档未强调反代后必须配置可信代理,否则全体用户共享代理 IP 的限流桶(互相误伤)且审计 IP 全是代理地址;反向配置错误则可伪造 XFF 绕过限流。 - ✅ 修复:
deploy/README.md新增「安全清单(生产部署必读)」7 条(可信代理、立即初始化/FCB_ADMIN_PASSWORD、修改组件默认凭据与端口发布、Postgres TLS、会话有效期、限流降级语义、内置清理任务);deploy/.env.example为FCB_TRUSTED_PROXIES、FCB_ADMIN_PASSWORD、FCB_ADMIN_SESSION_EXPIRE补充注释说明,弱凭据处(MinIO/WebDAV/Postgres)加「仅限本机冒烟,生产必须修改」警示。
三、提示 / 信息(Info)修复情况
version/storage_engine公开暴露(小信息收集面):保留(前端展示与诊断需要,风险极低)。- compose 弱凭据/端口发布:✅ 已在
.env.example与 README 安全清单加警示(凭据为示例值,端口建议删除或绑 127.0.0.1)。 robotsText无路由:✅ 已新增GET /robots.txt(router.go,输出配置内容,text/plain);端到端实测 200 且内容生效。adminFileListLIKE 未转义:✅ 已加escapeLike(\→\\、%→\%、_→\_)+ESCAPE '\'(admin-only,防御性修复)。- Postgres 并发配额超额记账:✅
reserveStorage包事务并对 Postgres 加pg_advisory_xact_lock("FCBQ" 键)串行化配额判定;SQLite 写串行不受影响。 crypto/rand错误忽略:✅GenerateJWTSecret已改 panic 显式失败(见 M1);generateCode/randomHex维持重试回退(非安全关键路径)。
四、确认到位的安全设计(无需修改)
- JWT:HS256 +
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 密码哈希迁移 bcrypt;M3 全局 MaxBytesReader;M5 init 计数 + 清理循环 | ✅ 已完成 |
| 计划 | L1–L10 按运营形态取舍 | ✅ 已全部修复 |
六、修复验证记录(2026-09-05 第二轮)
- 静态检查:
gofmt -l .无输出、go vet ./...通过、go build ./...通过。 - 单元/集成测试:
go test ./...全绿(api/audit/cache/config/database/middleware/settings/storage 全部 ok),新增修复回归测试:settings/sanitize_test.go(L7 净化器 19 例)middleware/audit_l5_test.go(L5 admin 审计落库)middleware/ratelimit_fallback_test.go(L9 缓存故障降级 fail-close)api/security_fixes_test.go(L4 开关强制、M3 chunk 上限、L3 提码长度、M2 confirm 大小/超限、M1 密码迁移)
- 依赖扫描:
govulncheck ./...0 可达漏洞;npm audit --omit=dev剩 2 moderate(vue-i18n 上游未发布修复,跟踪中)。 - 端到端冒烟(编译二进制 + SQLite 实跑):
/setup初始化 200(M1 生效:DB 中admin_token = bcrypt$$2a$12$…);- 错误密码登录 401 → 审计落
admin/denied;成功登录落admin/success(L5); - 连续 3 次错误后第 4 次起 423 锁定(L9 限流);
- 配置写入
<script>alert(1)</script>+javascript:链接 →/api/v1/config输出已剔除、合法<a href>保留(L7); /robots.txt200 且内容生效(Info3);- 文本分享创建 + 取件下载全链路 200。
- 遗留跟踪:
vue-i18n上游修复版本发布后升级(当前 npm audit 的 2 个 moderate 均来源于此)。