Files
full/docs/mgt.md

157 lines
13 KiB
Markdown
Raw Normal View History

2026-09-22 18:53:53 +08:00
# mgt后台组织与权限管理代码审计报告
| 项 | 内容 |
| --- | --- |
| 审计对象 | `module/base/mgt` |
| 服务域 | 基础与平台服务 |
| 审计日期 | 2026-09-22 |
| 代码规模 | 手写 Go 文件约 80 个(`cmd/``internal/` 全量、`service/``types/``middleware/``libs/`**无 proto 定义(纯 Gin REST无生成代码** |
| 入口 | `cmd/main`Gin 单进程)、聚合入口 `pkgs/all``pkgs/ecmall` |
| 对外协议 | 仅原生 REST基础前缀 `/rest/mgt`README 所述 `/mgt/v1/...` 与实际不符) |
| 结论摘要 | 鉴权中间件**已正确挂载**于全部管理路由JWT + 超级管理员角色双重校验模块内无绕过SQL 全部参数化。但 `Login``AppId/AppKey` 路径以**明文相等比对密码列**哈希即凭证dev 配置启用 `InitRootUser` 注入 `root/123456` 弱默认账号;列表查询**无分页上限**;配置文件注释与实现行为矛盾。 |
## 1. 服务定位与职责
后台 RBAC 权限管理系统:用户、角色、权限(菜单/按钮、应用、部门的管理与关联。负责管理员账号生命周期、登录认证、Token 刷新、短信重置密码、以及权限/角色/部门树的维护。它是**管理面admin console**服务,所有写操作需超级管理员权限。
## 2. 代码结构与入口
| 路径 | 职责 |
| --- | --- |
| `cmd/main/main.go` | 进程入口配置→impl→Gin→session→中间件→`routers.Register`→监听 |
| `internal/config/config.go` | 配置结构(`Databases/SecretKey/InitRootUser` 等);设置 JWT 密钥来源 |
| `internal/impl/{impl,with}.go` | 初始化 Redis/DB/Etcd/内存缓存DB 连接mysql/postgres+ 自动迁移 |
| `internal/middleware/rbac.go` | `RequireAdmin` / `IsSuperAdmin` / `EnsureSelfOrAdmin` 管理员鉴定 |
| `internal/libs/validator.go` | 参数校验(手机号、邮箱自定义规则) |
| `internal/routers/register.go` | REST 路由注册(匿名组 + 鉴权组 + 管理员组) |
| `internal/logic/hello/ping.go` | 健康检查、Session 调试端点 |
| `internal/logic/pub/{login,refresh,forget}.go` | 公开接口:登录、刷新、短信重置 |
| `internal/logic/user/*` | 用户 CRUD、角色/权限分配、查询 |
| `internal/logic/role/*``permission/*``application/*``department/*` | 各资源 CRUD 与关联 |
| `internal/logic/tool/tool.go` | 分页参数处理 |
| `internal/models/*` | 16+ 模型 + `impl.go`(迁移与 root 初始化) + `init_db.go`(root 数据) + `init_menu.go`(菜单) |
| `internal/types/{vars,req,resp}.go` | 常量、请求/响应结构 |
| `service/{expose,dependencies}.go` | 聚合宿主注入(共用 Engine 与连接) |
> 与 `logs`/`passport` 不同mgt **没有 proto/gRPC**,是纯 Gin 服务;聚合入口 `service.Expose` 直接复用同一 `Engine` 注册路由,鉴权接线一致。
## 3. 接口清单
> 鉴权分三层:`anonymous`(无)、`auth``middleware.JwtAuth`)、`admin``mgtmw.RequireAdmin`)。所有管理写操作均挂在 `admin` 组。
| 方法 | 路径 | 功能 | 鉴权 | 实现位置 |
| --- | --- | --- | --- | --- |
| GET | `/rest/mgt/ping` | 健康检查 | 无 | `logic/hello/ping.go` |
| GET | `/rest/mgt/session` | Session 调试 | 无 | `logic/hello/ping.go` |
| POST | `/rest/mgt/login` | 登录(账号密码 / AppId+AppKey | 无 | `logic/pub/login.go` |
| POST | `/rest/mgt/refresh` | 刷新 Token | auth | `logic/pub/refresh.go` |
| POST | `/rest/mgt/reset` | 短信验证码重置密码 | 无 | `logic/pub/forget.go` |
| POST | `/rest/mgt/user/role` `/app` `/pmn` `/pmn_tree` | 当前用户角色/应用/权限查询 | auth | `logic/user/*` |
| POST | `/rest/mgt/user/create` `/del` `/detail` `/modify` `/fetch` `/list` `/set_role` `/del_role` `/set_pmn` `/modify_pmn` `/del_pmn` | 用户管理(全部写操作) | admin | `logic/user/*` |
| POST | `/rest/mgt/app/*` | 应用管理 | admin | `logic/application/*` |
| POST | `/rest/mgt/role/*` | 角色管理 | admin | `logic/role/*` |
| POST | `/rest/mgt/pmn/*` | 权限管理 | admin | `logic/permission/*` |
| POST | `/rest/mgt/dpt/*` | 部门管理 | admin | `logic/department/*` |
> 对照发现README 描述的路径前缀 `/mgt/v1/...` 与实际注册前缀 `/rest/mgt/...` 不一致;`ForgetPwdByPwd`(旧密码改密)为**空 stub 且未注册路由**。
## 4. 数据模型与表
| 表名 | 字段 | 类型 | 键/约束 | 说明 |
| --- | --- | --- | --- | --- |
| `mgt_user` | `id` | - | PK | 用户 |
| | `account` | varchar(255) | uniqueIndex | 账号(含 `root` |
| | `phone` | varchar(255) | - | 手机号 |
| | `password` | varchar(255) | - | 密码bcrypt / 兼容 MD5 十六进制) |
| | `salt` | varchar(255) | - | 密码盐 |
| | `last_login_at` | time | - | 最后登录 |
| `mgt_role` | `id/name/description` | - | - | 角色(含 `超级管理员` |
| `mgt_permission` | `id/code/title/menu_path/type/app_id/parent_id` | - | - | 权限/菜单 |
| `mgt_application` | `id/workspace/title` | - | workspace 唯一 | 应用 |
| `mgt_department` | `id/name/parent_id/leader_id` | - | - | 部门 |
| 关联表 | `mgt_link_user_role` `mgt_link_user_pmn` `mgt_link_user_app` `mgt_link_user_dpt` `mgt_link_role_pmn` `mgt_link_role_app` `mgt_link_dpt_role` `mgt_link_dpt_pmn` | - | 复合唯一 | 多对多关联 |
## 5. 核心流程
```mermaid
flowchart TD
A["POST /rest/mgt/login"] --> B["按 account 或 AppId 查 mgt_user"]
B --> C{"AppKey 路径?"}
C -->|否| D["checkPwdAndStatus: bcrypt / MD5 比对"]
C -->|是| E["checkAppKeyAndStatus: 明文相等比对密码列"]
D --> F["签发 JWT"]
E --> F
G["POST /rest/mgt/user/create"] --> H["RequireAdmin 校验超级管理员"]
H --> I["bcrypt 哈希 + 事务建用户/关联"]
J["POST /rest/mgt/reset"] --> K["校验短信验证码(Redis)"]
K --> L["bcrypt 重置密码"]
```
## 6. 审计发现
### 6.1 安全
| 级别 | 位置 | 问题 |
| --- | --- | --- |
| **高** | `internal/logic/pub/login.go:147-159` | **`checkAppKeyAndStatus` 以明文相等比对密码列**`if userPwd != pwd` 直接比较存储的 `password` 字段与传入 `AppKey`。对 MD5 兼容老用户(密码列存 `md5(pwd+salt)` 十六进制),攻击者只要拿到密码哈希,以 `app_id=账号``app_key=哈希` 即可登录,**哈希本身成为可用凭证pass-the-hash**,加密存储失去意义。 |
| **高** | `etc/mgt_dev.yaml:16` + `internal/models/impl.go:69-70` + `internal/models/init_db.go:16` | **弱默认管理员账号**dev 配置 `InitRootUser: true`,启动时 `InitRootUserData()` 创建 `root` 账号、口令硬编码 `123456`。非生产环境一旦启用即注入后门级默认凭据;代码不强制改密。 |
| 中 | `internal/config/config.go:19,24-29` | **配置语义矛盾**:注释"未配置时默认 true",但实现 `InitRootUser==nil` 时返回 `false`README:245 又写"默认 false"。部署者若省略该字段,实际**不会**创建 root与注释和部分文档预期相反易误导安全预期、造成"无可用管理员"或反之。 |
| 中 | `internal/config/config.go:39` | **JWT 密钥来源不可见**`Spec.SecretKey = env.Runtime.JwtSecretKey`yaml 中的 `SecretKey: CHANGE_ME` 被覆盖忽略。真实密钥强度/是否强制设置取决于 bsm-sdk 与环境变量,本模块无法确认【信息不足】。 |
| 低 | `internal/logic/hello/ping.go:25-35` + `internal/routers/register.go:32` | `/rest/mgt/session` 为匿名调试端点:写入固定 session 并打印到 stdout属调试残留可被探测/泄露。 |
| 低 | `internal/logic/pub/login.go:108-116` | `SaveToken` 将 token 写入 Redis 时 TTL=0永不过期该函数未被任何路径调用死代码但一旦启用即永久缓存 token。 |
> 管理员鉴定本身**正确**`routers/register.go:42-43` 在管理路由挂 `RequireAdmin`,其 `IsSuperAdmin``internal/middleware/rbac.go:19-36`)按 `id` 回查 DB 确认账号为 `root` 或拥有 `超级管理员` 角色,**不以 JWT 声明直接判定**,模块内无明显绕过。唯一残留风险在于 SDK 的 `ParseAuth` 是否校验 JWT 签名(见 6.4)。
### 6.2 正确性与逻辑缺陷
| 级别 | 位置 | 问题 |
| --- | --- | --- |
| 中 | `internal/logic/tool/tool.go:5-14` | **列表查询无分页上限**`GetSizeAndPage``size<=0` 才取默认 10否则直接使用请求值。所有 `*Fetch`/`list` 接口user/role/app/pmn/dpt接受任意 `size`,可触发无上限批量查询(全表扫描 / DoS / 数据拖库)。 |
| 中 | `internal/config/config.go:24-29` | `InitRootUserEnabled` 在 nil 时返回 false与同文件注释及 README 宣称的"默认 true"不一致(同 6.1 中)。 |
| 中 | `internal/models/impl.go:106,113` | **SQL Debug 全量开启**mysql/postgres 连接均硬编码 `Debug: true`,所有 SQL含敏感查询进入日志生产环境应关闭。 |
| 低 | `internal/logic/user/create.go:57-72``internal/logic/application/create.go:50-68` | 创建时对已软删的同 phone/account或 workspace/title记录执行 `Unscoped().Delete` 硬删除再重建,可能误删历史数据且不可恢复。 |
### 6.3 未完成实现
- `internal/logic/pub/forget.go:103-104``ForgetPwdByPwd`(凭旧密码改密)为空 stub且未注册路由。
- `internal/logic/pub/login.go:108``SaveToken` 死代码(无调用点)。
- `internal/models/init_menu.go:16` 引用 `etc/menu.json`;本仓库 `etc/` 下为 `menu.json`(已存在)但 `scripts`/`other` 提及 `menu_structure.json`,文件缺失会导致 `InitRootUserData` 事务因 `Load` 失败而回滚、root 创建失败【信息不足,需确认部署包含该文件】。
- 全模块**无 Go 单元测试覆盖逻辑层**(仅 `libs` 校验;`test/` 下多为 `.http` 手工用例)。
### 6.4 健壮性与可维护性
| 级别 | 位置 | 问题 |
| --- | --- | --- |
| 中 | `internal/models/impl.go:106,113` | SQL Debug 全量日志(同 6.2),泄露查询结构。 |
| 低 | `internal/logic/user/del.go:16-78` | **无"禁止删除 root / 超级管理员"保护**:超级管理员可删除 `root` 或另一超级管理员导致管理面锁死DoS。 |
| 低 | `internal/logic/pub/login.go:80``user/create.go:46` 等多处 | 大量 `printer.Error/Info` 打印账号、手机号、内部错误等敏感信息,存在信息泄露。 |
| 低 | 全模块 | 逻辑层无单元测试;`config` 注释与实现、README 与真实路由前缀多处不一致,增加维护与误配风险。 |
| 【信息不足】 | `git.apinb.com/bsm-sdk/core/middleware.ParseAuth` | 管理员判定的前提是 JWT 签名被 `ParseAuth` 正确校验;该函数在 bsm-sdk 内,本模块不可见,无法确认签名校验是否到位(若未校验,可伪造 `id`/`account=root` 的 token 越权)。建议核实 SDK 实现。 |
## 7. 风险汇总
| 编号 | 级别 | 问题 | 影响面 |
| --- | --- | --- | --- |
| M1 | 高 | `AppKey` 登录明文比对密码列(哈希即凭证) | 密码哈希泄露即等于账号失陷 |
| M2 | 高 | dev 默认 `root/123456` 弱管理员账号 | 非生产环境被注入后门凭据 |
| M3 | 中 | `InitRootUser` 注释/实现/文档行为不一致 | 安全预期误导、root 可能未创建 |
| M4 | 中 | JWT 密钥来源不可见 | 密钥强度不确定 |
| M5 | 中 | 列表查询无分页上限 | 全表扫描 / DoS / 拖库 |
| M6 | 中 | SQL Debug 全量日志 | 查询与数据泄露 |
| M7 | 低 | 匿名 session 调试端点 | 探测/信息泄露 |
| M8 | 低 | 可删除 root、无单测、信息打印 | 健壮性与可维护性 |
| M9 | 【信息不足】 | SDK `ParseAuth` 签名校验未知 | 管理员越权潜在风险 |
## 8. 修复建议(务实项)
1. **移除 AppKey 明文比对**`checkAppKeyAndStatus``pub/login.go:147`)不应以 `userPwd != pwd` 直接比较密码列。应用密钥登录应走独立的应用密钥表/字段校验,而非复用用户登录密码;至少应与 `checkPwdAndStatus` 同样走 bcrypt/MD5 哈希比对,杜绝"哈希即凭证"。
2. **收敛默认账号**dev 之外不要启用 `InitRootUser`;若需启用,生成随机初始口令并强制首次登录改密,移除硬编码 `123456``init_db.go:16`)。
3. **统一配置语义**`config.go` 注释与 `InitRootUserEnabled` 实现保持一致(明确"省略即不初始化"或"省略即初始化"),并同步修正 README避免误导。
4. **分页上限**`tool.GetSizeAndPage``tool/tool.go`)对 `size` 设上限(如 ≤200所有 `*Fetch`/`list` 复用。
5. **关闭 SQL Debug**`models/impl.go``Debug: true` 改为按环境(非 dev 关闭)。
6. **删除调试端点与死代码**:移除 `/rest/mgt/session``register.go:32`)与 `SaveToken``login.go:108`)。
7. **删除保护**`user/del.go` 增加"禁止删除 root / 超级管理员"校验。
8. **确认 JWT 签名校验**:核实 bsm-sdk `ParseAuth` 是否校验签名;如未校验,管理员鉴定即形同虚设——此为前提性修复。
> 本报告只列出与现有实现直接相关的修复项,不引入新的分层或抽象封装。