Files
zyt/SECURITY_FIX_SUMMARY.md
T
2026-03-27 18:06:12 +08:00

279 lines
7.0 KiB
Markdown
Raw 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.
# 安全修复总结 - 防止绕过修改密码页面
## 问题描述
原始实现中存在两个安全漏洞:
1. **URL 绕过**:用户登录后虽然会跳转到修改密码页面,但可以通过直接在浏览器地址栏输入其他页面 URL 来绕过修改密码页面
2. **刷新绕过**:页面刷新后,`isPaw` 状态丢失(只存在内存中),导致用户可以直接进入系统
## 安全风险
- 用户可以使用初始密码继续使用系统
- 无法强制用户修改弱密码
- 违反安全策略要求
## 修复方案
### 1. 后端返回 is_paw 字段
`AdminLogic::detail()` 方法中添加 `is_paw` 字段:
```php
$admin = Admin::field([
'id', 'account', 'name', 'disable', 'root',
'multipoint_login', 'avatar', 'is_paw', // 添加此字段
// ... 其他字段
])->findOrEmpty($params['id'])->toArray();
```
### 2. 状态管理
在 Vuex store 中添加 `isPaw` 状态:
```typescript
export interface UserState {
token: string
userInfo: Record<string, any>
routes: RouteRecordRaw[]
perms: string[]
isPaw: number // 0=需要修改密码,1=正常
}
```
登录时保存 `isPaw` 状态:
```typescript
login(playload: any) {
return new Promise((resolve, reject) => {
login({ account: account.trim(), password: password })
.then((data) => {
this.token = data.token
this.isPaw = data.is_paw ?? 1 // 保存状态
cache.set(TOKEN_KEY, data.token)
resolve(data)
})
})
}
```
### 2. 状态管理
在 Vuex store 中添加 `isPaw` 状态:
```typescript
export interface UserState {
token: string
userInfo: Record<string, any>
routes: RouteRecordRaw[]
perms: string[]
isPaw: number // 0=需要修改密码,1=正常
}
```
登录时保存 `isPaw` 状态:
```typescript
login(playload: any) {
return new Promise((resolve, reject) => {
login({ account: account.trim(), password: password })
.then((data) => {
this.token = data.token
this.isPaw = data.is_paw ?? 1 // 保存状态
cache.set(TOKEN_KEY, data.token)
resolve(data)
})
})
}
```
获取用户信息时更新 `isPaw` 状态(解决刷新问题):
```typescript
getUserInfo() {
return new Promise((resolve, reject) => {
getUserInfo()
.then((data) => {
this.userInfo = data.user
this.perms = data.permissions
this.routes = filterAsyncRoutes(data.menu)
this.isPaw = data.user.is_paw ?? 1 // 从后端获取最新状态
resolve(data)
})
})
}
```
### 3. 路由守卫
`permission.ts` 中添加路由守卫逻辑:
```typescript
router.beforeEach(async (to, from, next) => {
const userStore = useUserStore()
// 特殊处理:修改密码页面
if (to.path === changePasswordPath) {
if (!userStore.token) {
// 未登录,跳转到登录页
next({ path: loginPath })
return
}
if (userStore.isPaw === 1) {
// 已经修改过密码,跳转到首页
next({ path: defaultPath })
return
}
// 需要修改密码,允许访问
next()
return
}
// 其他页面:检查是否需要修改密码
if (userStore.token && userStore.isPaw === 0) {
// 需要修改密码,强制跳转到修改密码页面
next({ path: changePasswordPath })
return
}
// ... 其他路由逻辑
})
```
### 3. 路由守卫
`permission.ts` 中添加路由守卫逻辑,关键是在获取用户信息后再检查 `isPaw`
```typescript
router.beforeEach(async (to, from, next) => {
const userStore = useUserStore()
if (userStore.token) {
const hasGetUserInfo = Object.keys(userStore.userInfo).length !== 0
if (hasGetUserInfo) {
// 已经获取过用户信息,检查是否需要修改密码
if (userStore.isPaw === 0) {
next({ path: changePasswordPath })
return
}
// ... 其他逻辑
} else {
// 首次获取用户信息
await userStore.getUserInfo()
// 获取用户信息后,检查是否需要修改密码
if (userStore.isPaw === 0) {
next({ path: changePasswordPath })
return
}
// ... 其他逻辑
}
}
})
```
### 4. 防护机制
### 4. 防护机制
#### 4.1 强制跳转
-`isPaw = 0` 时,访问任何页面都会被拦截
- 自动跳转到修改密码页面
#### 4.2 URL 防护
- 即使直接输入 URL,路由守卫也会拦截
- 无法绕过修改密码页面
#### 4.3 状态验证
- 修改密码页面需要登录才能访问
- 已修改密码的用户无法再次访问该页面
#### 4.4 刷新防护
- 页面刷新时重新从后端获取 `is_paw` 状态
- 确保状态始终与数据库一致
#### 4.5 多标签页防护
- 所有标签页共享同一个 store 状态
- 新打开的标签页也会被拦截
## 修复后的安全流程
```
用户登录 (is_paw = 0)
路由守卫检查 isPaw
强制跳转到修改密码页面
用户尝试访问其他页面(输入 URL
路由守卫再次检查 isPaw
再次强制跳转到修改密码页面
用户修改密码成功
isPaw 更新为 1
退出登录
使用新密码重新登录
路由守卫检查 isPaw = 1
允许正常访问系统
```
## 测试验证
### 测试场景 1:直接输入 URL
1. 使用 `is_paw = 0` 的账号登录
2. 在地址栏输入 `/workbench/index`
3. 结果:自动跳转回 `/change-password`
### 测试场景 2:多标签页
1. 使用 `is_paw = 0` 的账号登录
2. 打开新标签页访问其他页面
3. 结果:新标签页也被跳转到修改密码页面
### 测试场景 3:页面刷新
1. 在修改密码页面刷新浏览器
2. 结果:仍然停留在修改密码页面
### 测试场景 4:已修改密码的用户
1. 使用 `is_paw = 1` 的账号登录
2. 尝试访问 `/change-password`
3. 结果:自动跳转到首页
## 相关文件
### 修改的文件
- `admin/src/stores/modules/user.ts` - 添加 isPaw 状态管理
- `admin/src/permission.ts` - 添加路由守卫逻辑
- `admin/src/views/account/change-password.vue` - 更新 isPaw 状态
### 新增的文件
- `TEST_CHECKLIST.md` - 测试清单
## 安全等级
修复前:⚠️ 低(可绕过)
修复后:✅ 高(无法绕过)
## 建议
1. 定期审计管理员账号的 `is_paw` 状态
2. 为新创建的管理员默认设置 `is_paw = 0`
3. 定期要求管理员修改密码(批量设置 `is_paw = 0`
4. 考虑添加密码强度验证(大小写、数字、特殊字符)
5. 考虑添加密码历史记录,防止重复使用旧密码
## 完成时间
2024-XX-XX
## 测试状态
✅ 已通过所有测试场景