Files
FileShare/docs/security-audit-2026-09-05.md
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

187 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 第二轮):M1M5、L1L10 及可行动 Info 项已全部修复**,逐项见各条目「✅ 修复」标记;验证:`gofmt`/`go vet`/`go test ./...` 全绿,`govulncheck` 0 命中,二进制端到端冒烟(初始化→登录→审计落库→限流锁定→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` 改为 **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-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 模式完全不生效(内容不经过服务器)。
- 影响:开启游客上传 + S3 引擎的部署中,任意访客可绕过 `max_file_size``storageLimit`(配额按声明值记账),向桶内塞入任意大小/内容的对象(存储成本攻击、策略绕过)。
- ✅ 修复(多引擎一致,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. 测试:`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 消耗内存/磁盘/带宽。
- ✅ 修复:
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.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.0QPACK 扩张 DoS(实际未启用 HTTP/3 监听,实践影响低);
- `golang.org/x/net v0.45.0`8 个 imported 级漏洞(GO-2026-5030/5029/5028/5027/5026/5025/4918 等,含 HTTP/2 传输死循环),修复于 v0.53v0.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`(预留 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-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 万空间);测试 `custom_code_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",但服务端/前端均无净化。管理员账号被盗即可对全站访客注入脚本。
- ✅ 修复(双侧):
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-33``server/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-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)修复情况
1. `version`/`storage_engine` 公开暴露(小信息收集面):**保留**(前端展示与诊断需要,风险极低)。
2. compose 弱凭据/端口发布:✅ 已在 `.env.example` 与 README 安全清单加警示(凭据为示例值,端口建议删除或绑 127.0.0.1)。
3. `robotsText` 无路由:✅ 已新增 `GET /robots.txt``router.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` 维持重试回退(非安全关键路径)。
---
## 四、确认到位的安全设计(无需修改)
- **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 原子预留判定,防超卖。
- **部署**:多阶段构建、非 rootuid 10001)运行、`.dockerignore` 合理、`.env` 未含真实密钥(当前也非 git 仓库;初始化 git 后应将 `deploy/.env` 加入忽略清单)。
---
## 五、修复优先级建议(原计划,已全部落实)
| 优先级 | 事项 | 状态 |
|---|---|---|
| 立即 | M4 依赖升级;M2 的 confirm 大小校验 | ✅ 已完成 |
| 短期 | M1 密码哈希迁移 bcryptM3 全局 MaxBytesReaderM5 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 moderatevue-i18n 上游未发布修复,跟踪中)。
- **端到端冒烟**(编译二进制 + SQLite 实跑):
1. `/setup` 初始化 200(M1 生效:DB 中 `admin_token = bcrypt$$2a$12$…`);
2. 错误密码登录 401 → 审计落 `admin/denied`;成功登录落 `admin/success`L5);
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 均来源于此)。