153 lines
18 KiB
Markdown
153 lines
18 KiB
Markdown
# 总体审计报告(2026-09-22)
|
||
|
||
> 本报告是产品级汇总,汇总逐微服务审计结论。逐服务明细见 [`docs/`](../docs/README.md)(21 篇)。
|
||
|
||
## 一、审计范围与方法
|
||
|
||
| 项 | 内容 |
|
||
| --- | --- |
|
||
| 审计对象 | BSM Infra Full 后端 Go workspace,`go 1.27.1` |
|
||
| 覆盖模块 | `module/` 18 个业务服务 + `pkgs/all`、`pkgs/ecmall` 2 个聚合入口 + `scripts` 与工作区配置 |
|
||
| 代码量 | `module/` 903 个 `.go`(177 个 `.http`、66 个 `.proto`、58 个 `.yaml`)、`pkgs/` 54 个 `.go` |
|
||
| 方法 | 只读静态分析:逐文件读 `cmd/ internal/ service/ proto/ etc/`,生成代码 `pb/` 仅看接口签名 |
|
||
| 输出 | 每服务一份 `docs/<服务名>.md`;本文与《产品与服务目录》为产品级汇总 |
|
||
|
||
**审计约定**:结论均带 `文件:行号`;无法确认处以【信息不足】标注;修复建议只给最小、直接动作,**不含过度设计/过度封装方案**。
|
||
|
||
## 二、结论摘要
|
||
|
||
架构组织清晰(服务域划分、`service.Expose` 注入、聚合入口只做注册表取舍),但**实现质量与完整性存在系统性缺口**:本仓库当前状态更接近"可编译的接口骨架",而非可上线的生产系统。
|
||
|
||
- **认证与鉴权**:至少 5 个模块的验证码校验形同虚设(passport、mall);多处接口只校验"是否登录"而不校验"是否有权操作该资源"(address、cms、group、relation、feed、mall、market、feedback、order);
|
||
- **资金链路**:wallet 支付回调不验签、提现不扣减、余额扣减读后写,order 模拟支付/发货生产可达且优惠券计算写反,**存在直接资金损失路径**;
|
||
- **数据正确性**:多处引用模型中不存在的列名(`relation_id`/`creator_id`/`createor_id`/`enabled` 等)与错误键名,导致接口恒失败或静默返回空;
|
||
- **上线阻断**:聚合入口无生产配置、JWT/session 密钥默认值可直接启动、首批部署不建表不播种、`scripts/api-docgen` 无法构建;
|
||
- **测试**:全仓仅 21 个 `*_test.go`,base 多数模块、wallet、social 三服务**零单测**,上述缺陷几乎无自动化拦截。
|
||
|
||
## 三、严重问题台账
|
||
|
||
### P0 · 严重(认证绕过 / 资金损失 / 数据越权 / 上线即故障)
|
||
|
||
| 编号 | 服务 | 问题 | 证据 | 影响 |
|
||
| --- | --- | --- | --- | --- |
|
||
| S1 | passport | 验证码登录/快捷登录/注册/找回**均不校验验证码** | `login/code.go:16-59`、`login/quick.go:20-64`、`register/code.go`、`forget/verify.go` | 任意手机号 + 任意 code 登录/批量建号,账号体系失守 |
|
||
| S2 | passport | 找回密码仅凭 `identity` 重置,且写**未加盐 MD5** | `forget/reset.go:17-41,31` | 任意账号接管;重置后密码无法通过 bcrypt 登录 |
|
||
| S3 | mgt | `AppKey` 登录**明文相等比对密码列** | `pub/login.go:147-159` | pass-the-hash,哈希泄露即可登录 |
|
||
| S4 | mgt | dev 配置注入默认管理员 `root/123456` | `etc/mgt_dev.yaml:16`、`internal/models/init_db.go:16` | 默认弱口令后台 |
|
||
| S5 | wallet | 支付回调**不验签**,用调用方传入状态置成功 | `internal/logic/payment/callback.go:16-31` | 客户端自证支付成功;渠道回调 `WxCallback` 反为 `// Todo` 不落库(`wechat/wx_callback.go:38`) |
|
||
| S6 | wallet | 提现**只校验余额不扣减**、无幂等键 | `internal/logic/basic/apply_cash.go:35-49` | 余额可重复开单超额提现;`ChargeWallet` 全仓无调用点→充值链路不通 |
|
||
| S7 | wallet | 余额扣减为读后写、`WHERE identity=?` 无条件更新 | `internal/logic/basic/wallet.go:53-63` | 并发双花 |
|
||
| S8 | order | **模拟支付/发货/收货生产可达**且硬编码支付参数 | `logic/summary/simulate_pay.go:25`、`proto/summary.proto` | 直接伪造支付并推进订单状态 |
|
||
| S9 | order | `OrderApprove` 完全无鉴权 | `logic/mgt/order_approve.go:17` | 匿名审批退款并改写库存 |
|
||
| S10 | order | 优惠券金额**做加法**加入订单总额 | `logic/summary/confirm.go:72` | 用户应付变高;归属/有效期/门槛不校验 |
|
||
| S11 | order | 扣库存不检查 `RowsAffected` | `logic/summary/submit.go:209-217` | 超卖 |
|
||
| S12 | mall | 员工验证码登录**不校验验证码** | `internal/logic/staff/login.go:58` | 任意手机号接管店铺账号 |
|
||
| S13 | mall | 店铺**支付/邮件凭据任意登录用户可读改** | `logic/store/get_payment.go`、`set_payment.go`、`get_email.go`、`set_email.go`、`get_setting.go` | 渠道凭据外泄/被篡改 |
|
||
| S14 | market | 审核**无鉴权**、开号/删号鉴权被整段注释 | `agency/approve.go:15-46`、`agency/create.go:20-23`、`agency/delete.go:17-21` | 自助开号 + 自助审核 + 匿名封停他人 |
|
||
| S15 | market | 登录令牌用 AES-CBC、鉴权用 HS256 JWT,互不兼容 | `agency/login.go:53` vs `ParseMetaCtx`;SDK `crypto/token/jwt.go:68` | 合法 token 过不了任何接口;解析失败后访问 nil Claims → panic 可打崩进程 |
|
||
| S16 | initial | `os` 参数**字符串拼进 ORDER BY** | `check/config_cache.go:21` | SQL 注入 |
|
||
| S17 | initial | 版本判断用字符串不相等即"有更新" | `check/updates.go:39` | 客户端版本高于服务端记录时被强制降级 |
|
||
| S18 | group | 入群审批**无角色校验**(丢弃调用者身份) | `member/join_do_handle.go:19,24-52` | 任意登录用户可拉人入群/拒绝申请 |
|
||
| S19 | group | `Modify` 重写群 `identity` 且 `UpdateColumns` 不过滤零值 | `basic/modify.go:33-35` | 群成员/申请立即脱钩,"群消失" |
|
||
| S20 | relation | 通过好友申请时**不与申请记录比对** | `friend/apply_do_pass.go:24-47` | 单方面把任意陌生人加为好友 |
|
||
| S21 | cms | 文章/评论 `Delete/Modify` **不校验归属** | `post/delete.go:29`、`modify_comment.go:33`、`delete_comment.go:24` | 任意登录用户删改他人内容 |
|
||
| S22 | address | `Get/Modify/Delete` **无 owner 约束** | `library/get.go:19,29`、`modify.go:39`、`delete.go:19,24` | 遍历自增 id 获取他人姓名/手机号/详细住址并可改删 |
|
||
| S23 | cloud | 空间 `KeyIdentifier` 全局唯一 + 写入字面量 `"default"` | `models/cloud_space.go:12`、`space/get.go:29-50` | 首名用户之后所有人拿不到空间 |
|
||
| S24 | cloud | 私人资料"加密"名不副实(key 由客户端传入、服务端不加密) | `logic/private/encrypt_data.go`、`create_private_data.go:54` | 服务端明文存"加密"数据 |
|
||
| S25 | ads / feedback / logs | 三个模块业务接口全挂**匿名组** | `logs/internal/routers/register.go:21-27`、`ads`、`feedback` | 可伪造写入、全量读取 |
|
||
| S26 | logs | `fetch` 键名不一致 + `level` 类型断言**双 panic**,分页参数失效,`create` IP 过滤写反 | `logic/log/fetch.go:42,45,33-53`、`create.go:21` | 接口 500 / 全表返回 / 防护失效 |
|
||
| S27 | feed | 动态**越权删改** + 私密动态泄露 | `post/remove.go:25`、`change.go:40`、`models/query.go:30-37,60-71` | 删除他人动态;`is_open=false` 内容仍被返回 |
|
||
| S28 | pkgs/all · ecmall | JWT 密钥默认值恰为 32 字节,直接通过长度校验 | `etc/default_dev.yaml:22`、`internal/config/config.go:66-69` | 可自行签发任意身份 token |
|
||
| S29 | pkgs/all · ecmall | grpc-gateway **404 与业务 NotFound 无法区分** | `internal/server/server.go:63-71` | 所有"记录不存在"分支的真实响应被 Gin 纯文本 404 覆盖 |
|
||
| S30 | sender | 腾讯云短信**空实现** `return nil,nil` | `internal/logic/sms/send.go:151-153` | 接口返回 200 + `null`,调用方误判成功 |
|
||
|
||
### P1 · 高
|
||
|
||
| 编号 | 服务 | 问题 | 证据 |
|
||
| --- | --- | --- | --- |
|
||
| H1 | sender | 日发送量限制与短信黑名单**从未生效**(只有读、无写入点) | `sms/send.go:37,42-51` |
|
||
| H2 | sender | 验证码 `SetNX` 忽略返回值却发码;`Verify` 成功不删码可重放;用 `math/rand` | `sms/send.go:62-65,81,169`、`verify.go:28-32` |
|
||
| H3 | passport | 密码哈希用 `bcrypt.MinCost`(cost=4);Token 缓存键 `/TOKEN/` 与 `/token/` 不一致 | `register/do.go:30`、`account/set_password.go:35`、`vars/token.go:3` vs `register/do.go:84` |
|
||
| H4 | cloud | 容量配额只统计不限制;分享密码明文;`CreateShare` 不校验 `resource_id` 归属;根目录 `*ParentID` 空指针 panic | `space/*`、`logic/share/*`、`get_dir.go:91`、`get_dir_tree.go:97` |
|
||
| H5 | cms | 评论发布 `authName["name"].(string)` panic;赞踩无去重可刷负;`Search` 无分页上限 | `add_comment.go:35-40`、`query.go:346`、`incr_post_like.go:38` |
|
||
| H6 | address | `Fetch` 命名返回值未赋值恒返回空;默认地址唯一性无事务保障 | `library/fetch.go:23-40`、`create.go:41-45` |
|
||
| H7 | market | `Offset` 在归一化前计算、`Limit(0)` 致待审核列表恒空;三处 `Count` 结果被丢弃 | `agency/fetch.go:19,27-32`、`pending.go:31` |
|
||
| H8 | group | `creator_id`/`createor_id` 列不存在→核心接口恒返回 `ErrDB`、免验证入群永久失效 | `modify.go:35`、`do_quit.go:24`、`do_set_manager.go:25`、`models/query.go:39-43` |
|
||
| H9 | relation | `TagFetch` 必然 panic;`Fetch/ApplyFetch` 返回 `(nil,nil)`;7 个接口引用不存在的 `relation_id` 列;`Undo/DoIgnore` 无归属删除 | `tag_fetch.go:22`、`friend/fetch.go:47-49`、`delete.go:27` 等、`follow/undo.go:18,26`、`match/do_ignore.go:19,27` |
|
||
| H10 | feed | 分页写成 `Limit(int(page))` 且未回写;`DeleteComment` 是空函数;`action_op` 误写 `action_type` | `models/query.go:67,122-125,50` |
|
||
| H11 | wallet | 支付宝分支 `AliPay.Body` 为 nil 直接调用→必 panic;改支付密码不校验旧密码;微信 APIv3 密钥/商户号**明文入库**;`wallet_prod.yaml` 字段名不匹配无法启动 | `alipay/alipay.go:46-53`、`basic/set_pay_password.go:23-29`、`etc/wallet_prod.yaml:56` |
|
||
| H12 | order | 跨服务直连同库(`mall_product`/`mall_product_spec`/`mall_store`/`address_library`) | 8 个文件,散见 `logic/summary/*` |
|
||
| H13 | mall | 12 处写操作无店铺归属校验(商品/规格/图片/分类/广告/公告/运费/员工) | `product/item_modify.go:29`、`spec_delete.go:28` 等 |
|
||
| H14 | initial | `areas_cache.go` 查询模型不存在的列且 DB 错误被覆盖→静默空列表并缓存 30 分钟 | `areas_cache.go:33-35` |
|
||
| H15 | pkgs/all · ecmall | 无 prod 配置(`BSM_RuntimeMode=prod` 时 `log.Fatalf`);`build-all-linux.sh` 不覆盖聚合入口 | `pkgs/all/etc/` 仅 `default_dev.yaml`;`build-all-linux.sh:7,30` |
|
||
| H16 | pkgs/ecmall | 聚合路径不建表不播种,`Mgt.InitRootUser` 在聚合下不生效 | `service/*.go` 无 `NewImpl/AutoMigrate/models.New` 调用 |
|
||
| H17 | mock/order | `OrderModify` 占位但返回成功;social 三服务未接入聚合且 `Mux` 恒为 nil | `order/logic/mgt/order_modify.go`;`social/*/internal/server/new.go` |
|
||
| H18 | scripts | `scripts/api-docgen` 当前**无法构建**,且与工作区依赖版本长期漂移 6 处 | `scripts/api-docgen/generate.sh:5-7`;实测 `GOWORK=off go build ./...` 报 `updates to go.mod needed` |
|
||
| H19 | workspace | `go.work` 与 20 处 `go.mod` 把 SDK 指向**仓库外相对路径**且 `go.sum` 无校验和 | `go.work:26`、各 `go.mod` replace |
|
||
| H20 | scripts | `verify-workspace.*` 的 gofmt 门禁漏 `scripts/`、对空模块列表静默成功;缺 `.gitattributes` 致 CRLF 文件使门禁失败 | `verify-workspace.ps1/sh`;实测 `gofmt -l` 报 `module/base/fts/internal/routers/register_test.go` |
|
||
|
||
### P2 · 中
|
||
|
||
- **feedback / market / order / mall 等多处列表接口无分页上限**(可全表扫描/拖库):`feedback` 各方法、`mgt/internal/tool/tool.go:5-14`、`mall`、`cms` 等。
|
||
- **占位实现仍返回成功**:group `Basic.Disband`(`disband.go:26-31`)、order `OrderModify`、feed 4 类 Timeline + `Tag.PostList` + `Post.CommentList` + `Setting.Info/Rights`(9 个占位 RPC)、wallet 7 个占位 RPC、mall 入驻/授权/小程序码/运费、market `Data` 全 5 文件、cms `Site` 全系列、cloud 部分——统计见《产品与服务目录》。
|
||
- **云解析/错误吞没**:passport `account/get.go:44` 未处理 error;relation `tag_member_fetch`;group `createor_id` `.Scan` 错误丢弃。
|
||
- **GORM 链式未回写 `tx` / `First`+`Group` 语义不确定**:logs、feedback 等。
|
||
- **调试残留**:`logs/routers/register.go:20` `fmt.Println`、feed 多处 `fmt.Print`。
|
||
- **鉴权口径不一致**:模块 yaml 匿名白名单、聚合 `Authorization.Anonymous` 白名单与模块实际声明三者互不吻合(ads/feedback/passport 均出现)。
|
||
|
||
### P3 · 低
|
||
|
||
- 死代码与未用初始化:ads `RedisService/MemorySerice` 零引用;logs `RedisService/EtcdService` 未用、`Dependencies.Cache` 未应用;relation `follow.Do` 死代码。
|
||
- `internal/excode/ex.go` 为空文件;多处 crate 级常量与配置注释不一致。
|
||
- `test/grpc/main.go` 硬编码明文 JWT。
|
||
|
||
## 四、按服务域的问题分布
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
A["认证与账号<br/>passport · mgt · mall.staff"] --> A1["验证码不校验<br/>默认密码<br/>pass_hash"]
|
||
B["资金与订单<br/>wallet · order"] --> B1["回调不验签<br/>提现不扣减<br/>优惠券算反<br/>超卖"]
|
||
C["资源越权<br/>address · cms · group · relation · feed · mall · market · feedback"] --> C1["只校验登录<br/>不校验归属/角色"]
|
||
D["聚合与上线<br/>pkgs/all · pkgs/ecmall · scripts"] --> D1["密钥默认值<br/>404 分流<br/>无 prod 配置<br/>docgen 不可构建"]
|
||
```
|
||
|
||
## 五、共性根因
|
||
|
||
1. **鉴权只到"认证"不到"授权"**:大量写接口只判断调用者已登录,缺少"该资源是否属于调用者 / 调用者是否为管理员"的条件。越权类问题占本次 P0 的近一半,且分布在 9 个模块,属同一模式。
|
||
2. **验证码体系形同虚设**:passport、mall 的登录/注册路径根本不调用验证码比对;sender 的码生成(`SetNX` 忽略返回值)、校验(成功不删码)、限额(无写入点)三处同时失效。**即使调用方正确接入,也无法形成有效校验**。
|
||
3. **资金链路缺三要素**:无验签、无事务、无幂等。`wallet` 与 `order` 各自独立缺失,且模拟支付接口在生产可达。
|
||
4. **配置与代码长期漂移**:yaml 字段名与 `SrvConfig` 不匹配(wallet/feed 需 `log.Fatalf`/`panic` 才能启动)、匿名白名单三处口径不一致、占位实现返回成功、生产配置缺失。这些在"能编译/能 dev 跑"阶段不会暴露。
|
||
5. **列名/键名错误被吞没**:模型与实际表列不一致(`relation_id`/`creator_id`/`createor_id`/`enabled`/`show_town`/`sort_order`),叠加 `.Scan` 错误被丢弃、命名返回值 `reply` 未赋值,形成"接口 200 但返回空/报错被掩盖"的静默故障。
|
||
6. **测试覆盖极低**:全仓 21 个 `*_test.go` 集中在 `fts`/`mgt`/`sender`/`mall`/`market`/`order`/`pkgs`;`base` 多数模块、`wallet`、`social` 三服务零单测。上述缺陷几乎没有任何自动化拦截。
|
||
|
||
## 六、修复优先级路线(务实)
|
||
|
||
按"止血 → 阻断上线 → 修恒失败 → 补护栏"推进,全部为对现有函数/配置的最小改动:
|
||
|
||
1. **资金与认证止血(最高优先)**
|
||
- wallet:回调按渠道验签、金额与服务端记录核对、加幂等键;提现改为事务内扣减/冻结;余额扣减改条件更新(`WHERE balance >= ?`)。
|
||
- passport / mall:登录、注册、找回路径补回验证码比对;`Forget.Reset` 必须校验一次性令牌。
|
||
- mgt:移除默认管理员密码,改必填环境变量;`AppKey` 登录改为哈希比对。
|
||
- order:`Simulate*` 与 `OrderApprove` 加鉴权与生产开关(默认关闭);优惠券改为减法并校验归属/门槛/有效期;扣库存检查 `RowsAffected`。
|
||
2. **越权批量化修复**
|
||
- 统一给"按主键操作"的写接口补条件:address/cms/group/relation/feed/mall/market/feedback 分别补 `passport_id`/`owner`/店铺归属/群管理员角色。
|
||
3. **上线阻断项**
|
||
- pkgs:修复 404 分流判定(先读业务响应再回退);JWT/session 密钥去除可用默认值;补 prod 配置;聚合路径补建表/播种。
|
||
- scripts:修复 `api-docgen` 构建、纳入工作区或固定版本、gofmt 门禁补齐,SDK 改为可解析的版本依赖或明确文档化本地前提。
|
||
4. **恒失败接口修复**
|
||
- 修正不存在的列名/键名(group/relation/initial/feed/logs),修复命名返回值 `reply` 未赋值(address/relation),补齐 `main.go` 的 HTTP Mux 与聚合注册(social)。
|
||
5. **补护栏**
|
||
- 优先为认证、权限、支付、订单状态机、库存、验证码写单测;CI 增加 workspace 验证、凭据扫描、proto 一致性与依赖漏洞扫描。
|
||
|
||
## 七、正向结论
|
||
|
||
- **密码哈希**:market、mall 的账号创建/登录/改密已从 MD5 加盐迁移到 bcrypt(历史 MD5 需通过重置流程迁移)。
|
||
- **mgt 鉴权**:管理路由已正确挂载 `RequireAdmin`→`IsSuperAdmin`(按 DB 回查超级管理员角色),模块内未见绕过;其短信重置密码实现正确校验 Redis 验证码,与 passport 形成对比。
|
||
- **cloud 数据隔离**:所有 DB 查询均为 GORM 参数化(无 SQL 拼接/注入),接口以 `passport_id = auth.ID` 隔离,未发现跨用户越权读取他人目录/相册/笔记/书签/私人资料。
|
||
- **pkgs 动态 RPC**:具备 4 MiB 请求体上限、头部白名单透传、方法描述符缓存;两个聚合入口有针对鉴权/动态 RPC/配置/路由的单元测试。
|
||
- **2026-08-10 审计的整改**:绝对路径 `replace`、样例固定 secret 已处理(本次替换为仓库外相对路径,可移植性问题转为 P1)。
|
||
|
||
## 八、与 2026-08-10 审计报告的关系
|
||
|
||
`wiki/audit-2026-08-10.md` 聚焦"workspace 结构、样例凭据、进程与端口防护",其列出的 P0/P1 多数已整改。本报告在其基础上**下沉到业务实现层**,发现的 P0 数量与严重度显著高于前次——原因是前次未逐接口审计业务逻辑。两份报告不冲突,应合并为同一整改清单跟踪。
|