feat: 新增补丁管理和异常检测插件及相关功能

feat(protocol): 添加补丁管理和行为指标协议类型
feat(client): 实现补丁管理插件采集功能
feat(server): 添加补丁管理和异常检测API
feat(database): 新增补丁状态和异常检测相关表
feat(web): 添加补丁管理和异常检测前端页面
fix(security): 增强输入验证和防注入保护
refactor(auth): 重构认证检查逻辑
perf(service): 优化Windows服务恢复策略
style: 统一健康评分显示样式
docs: 更新知识库文档
This commit is contained in:
iven
2026-04-11 15:59:53 +08:00
parent b5333d8c93
commit 60ee38a3c2
49 changed files with 3988 additions and 461 deletions

255
wiki/SECURITY-AUDIT.md Normal file
View File

@@ -0,0 +1,255 @@
# CSM 安全审计报告
> **审计日期**: 2026-04-11 | **审计范围**: 全系统 (Server + Client + Protocol + Frontend)
> **方法论**: OWASP Top 10 (2021), CWE Top 25, 手动源码审查 + 攻击者视角分析
---
## 执行摘要
| 严重级别 | 数量 | 说明 |
|----------|------|------|
| **CRITICAL** | 4 | 可直接被远程利用,导致系统完全沦陷 |
| **HIGH** | 12 | 需特定条件但影响重大 |
| **MEDIUM** | 12 | 有限影响或需较高权限 |
| **LOW** | 8 | 纵深防御建议 |
**最关键发现**: JWT Secret 硬编码在版本控制中、注册 Token 为空允许任意设备注册、凭据文件无 ACL 保护、默认无 TLS 传输加密。这四个问题组合意味着攻击者可以在数分钟内完全接管系统。
---
## CRITICAL (4)
### AUD-001: JWT Secret 硬编码在 config.toml 中
- **文件**: `config.toml:12``crates/server/src/config.rs`
- **CWE**: CWE-798 (硬编码凭证)
- **OWASP**: A07:2021 - 安全配置错误
**漏洞代码**:
```toml
jwt_secret = "39ffc129-dd62-4eb4-bbc0-8bf4b8e2ccc7"
```
**攻击场景**: 任何能访问仓库的人可提取 secret为任意用户含 admin伪造 JWT获得系统完全管理控制权可推送恶意配置到所有医院终端、禁用安全控制、篡改审计记录。
**修复**:
1.`config.toml` 中移除硬编码 secret
2. 通过 `CSM_JWT_SECRET` 环境变量独占加载
3. secret 为空时拒绝启动
4. **立即轮换**已泄露的 secret `39ffc129-dd62-4eb4-bbc0-8bf4b8e2ccc7`
5.`config.toml` 加入 `.gitignore`
---
### AUD-002: 空 Registration Token — 任意设备注册
- **文件**: `config.toml:1`, `crates/server/src/tcp.rs:549-558`
- **CWE**: CWE-306 (关键功能缺失认证)
- **OWASP**: A07:2021
**漏洞代码**:
```toml
registration_token = ""
```
```rust
if !expected_token.is_empty() { // 空字符串直接跳过验证
```
**攻击场景**: TCP 端口 9999 可达的任何攻击者可注册恶意设备,注入伪造审计数据掩盖安全事件,获取所有插件配置(含安全策略)。
**修复**: 设置强 registration token空 token 时拒绝启动。
---
### AUD-003: Windows 凭据文件无 ACL 保护
- **文件**: `crates/client/src/main.rs:280-283`
- **CWE**: CWE-732 (关键资源权限不当)
- **OWASP**: A01:2021
**漏洞代码**:
```rust
#[cfg(not(unix))]
fn write_restricted_file(path: &std::path::Path, content: &str) -> std::io::Result<()> {
std::fs::write(path, content) // 无任何 ACL 设置
}
```
**攻击场景**: `device_secret.txt`HMAC 密钥)和 `device_uid.txt`(设备身份)以默认权限写入,设备上任何用户进程可读取。攻击者可提取密钥伪造心跳,在不同机器上模拟设备。
**修复**: 使用 `icacls``SetSecurityInfo` 设置仅 SYSTEM 可访问的 ACL。
---
### AUD-004: 默认无 TLS — 明文传输所有敏感数据
- **文件**: `config.toml` (无 `[server.tls]`), `crates/server/src/tcp.rs:411-414`
- **CWE**: CWE-319 (明文传输敏感信息)
- **OWASP**: A02:2021
**攻击场景**: TCP 端口 9999 以明文运行。`device_secret`、所有插件配置、Web 过滤规则、USB 策略、软件黑名单均以明文 JSON 传输。网络嗅探者可提取设备认证密钥并逆向工程安全策略。
**修复**: 生产环境强制 TLS无 TLS 时拒绝启动(`CSM_DEV=1` 除外)。
---
## HIGH (12)
### AUD-005: Refresh Token 未存储 — 撤销机制不完整
- **文件**: `crates/server/src/api/auth.rs:131-133`
- **CWE**: CWE-613
- `refresh_tokens` 表存在但从未写入。登录不存储 token record刷新仅检查 family 是否撤销,不验证 token 是否曾被实际颁发。无法强制注销所有 session。
### AUD-006: Refresh Token Family Rotation 存在 TOCTOU 竞争条件
- **文件**: `crates/server/src/api/auth.rs:167-183`
- **CWE**: CWE-367
- 检查 family 撤销状态与执行撤销之间无事务保护。并发使用同一 stolen token 的两个请求均可通过检查,攻击者获得全新的未撤销 token family。
### AUD-007: 客户端不验证服务器身份
- **文件**: `crates/client/src/network/mod.rs:149-162`
- **CWE**: CWE-295
- 客户端盲目连接任何响应的服务器。ARP/DNS 欺骗攻击者可推送恶意配置禁用所有安全插件、注入有害规则。HMAC 仅保护心跳,不保护配置推送。
### AUD-008: PowerShell 命令注入面
- **文件**: `crates/client/src/asset/mod.rs:82-83`, `crates/client/src/clipboard_control/mod.rs:143-159`
- **CWE**: CWE-78
- `powershell_lines()` 通过 `format!()` 拼接命令参数。若服务器推送含引号/转义字符的恶意规则,可能导致 PowerShell 命令注入。
### AUD-009: 服务停止/卸载未受保护
- **文件**: `crates/client/src/service.rs:17-69`
- **CWE**: CWE-284
- `csm-client.exe --uninstall` 无认证保护。终端管理员权限用户可完全移除安全代理,绕过所有监控。无服务恢复策略、无看门狗进程、无反调试保护。
### AUD-010: JWT Token 存储在 localStorage
- **文件**: `web/src/lib/api.ts:25-28, 175-176`
- **CWE**: CWE-922
- Access token 和 refresh token 均存储在 `localStorage`,可被同源任意 JS 访问。XSS 漏洞可直接窃取 7 天有效期的 refresh token。
### AUD-011: CSP 允许 unsafe-inline + unsafe-eval
- **文件**: `crates/server/src/main.rs:142-143`
- **CWE**: CWE-693
- `script-src 'self' 'unsafe-inline' 'unsafe-eval'` 使 CSP 对 XSS 几乎无效。结合 localStorage token 存储,单个 XSS 即可导致管理员会话完全沦陷。
### AUD-012: WebSocket JWT 在 URL 查询参数中
- **文件**: `crates/server/src/ws.rs:36-73`
- **CWE**: CWE-312
- JWT 通过 `/ws?token=eyJ...` 传输。Token 出现在浏览器历史、服务器访问日志、代理日志中。且 WebSocket handler 不检查用户角色,非管理员可接收所有广播事件。
### AUD-013: 告警规则 Webhook SSRF
- **文件**: `crates/server/src/api/alerts.rs:115-131`
- **CWE**: CWE-918
- `notify_webhook` 字段无 URL 验证。可设置为 `http://169.254.169.254/latest/meta-data/` (AWS 元数据) 或 `file:///etc/passwd`,将服务器变成 SSRF 代理。
### AUD-014: 仅基于用户名的速率限制可绕过
- **文件**: `crates/server/src/api/auth.rs:101`
- **CWE**: CWE-307
- 速率限制仅以用户名为 key。攻击者可用 `Admin``ADMIN` 等变体绕过。无 IP 限制,可分布式暴力破解。
### AUD-015: 磁盘加密确认在只读路由层 — 权限提升
- **文件**: `crates/server/src/api/plugins/mod.rs:42`
- **CWE**: CWE-862
- PUT `acknowledge_alert``read_routes()` 中(仅需认证,不需 admin。任何认证用户可确认忽略加密告警掩盖合规违规。
### AUD-016: 初始管理员密码输出到 stderr
- **文件**: `crates/server/src/main.rs:270-275`
- **CWE**: CWE-532
- 初始密码通过 `eprintln!` 输出。容器化部署中 stderr 被日志聚合系统捕获,有日志访问权限者可获取管理员密码。
---
## MEDIUM (12)
| # | 发现 | 文件 | CWE |
|---|------|------|-----|
| AUD-017 | 多个 Update handler 跳过输入验证 (软件黑名单/Web过滤器/剪贴板) | `software_blocker.rs:79`, `web_filter.rs:62`, `clipboard_control.rs:107` | CWE-20 |
| AUD-018 | USB 策略 rules 字段接受任意 JSON 无验证 | `usb.rs:94-135` | CWE-20 |
| AUD-019 | 密码无最大长度限制 (bcrypt 72 字节截断) | `auth.rs:303` | CWE-20 |
| AUD-020 | 多个字段缺少长度验证 (弹出窗口/剪贴板/USB策略名) | 多处 | CWE-20 |
| AUD-021 | 多个列表端点无分页 (黑名单/白名单/规则/策略) | 多处 | CWE-770 |
| AUD-022 | 磁盘加密状态列表无分页可全库转储 | `disk_encryption.rs:12-55` | CWE-770 |
| AUD-023 | JWT 角色仅信任 claim 不查库 (降级延迟) | `auth.rs:273` | CWE-863 |
| AUD-024 | 缺少 HSTS 头 | `main.rs:123-143` | CWE-319 |
| AUD-025 | CORS 配置需严格限制 | `main.rs:284-301` | CWE-942 |
| AUD-026 | 日志中泄露设备 UID 和服务器地址 | `main.rs:62`, `network/mod.rs:34` | CWE-532 |
| AUD-027 | 注册 Token 回退空字符串 | `main.rs:72` | CWE-254 |
| AUD-028 | conflict.rs 中 format! SQL 模式 (当前安全但脆弱) | `conflict.rs:205` | CWE-89 |
---
## LOW (8)
| # | 发现 | 文件 |
|---|------|------|
| AUD-029 | 组名未过滤 HTML 特殊字符 | `groups.rs:72-96` |
| AUD-030 | 弹出窗口阻止器更新可创建无过滤器的规则 | `popup_blocker.rs:67-104` |
| AUD-031 | 设备删除非原子 (自毁帧在事务前发送) | `devices.rs:215-306` |
| AUD-032 | 受保护进程列表硬编码且可修补绕过 | `software_blocker/mod.rs:9-73` |
| AUD-033 | hosts 文件修改可能与 EDR 冲突 | `web_filter/mod.rs:59-93` |
| AUD-034 | 软件拦截器 TOCTOU 竞争条件 (已缓解) | `software_blocker/mod.rs:329-386` |
| AUD-035 | 前端路由守卫不验证 JWT 签名 | `router/index.ts:38-49` |
| AUD-036 | WebSocket 不验证入站消息 (当前丢弃) | `ws.rs:108-119` |
---
## 修复优先级
### P0 — 立即 (24h)
| 修复项 | 对应发现 | 工作量 |
|--------|---------|--------|
| 轮换 JWT Secret移至环境变量 | AUD-001 | 30min |
| 设置非空 registration_token | AUD-002 | 15min |
| 凭据文件添加 Windows ACL | AUD-003 | 1h |
| 生产环境强制 TLS | AUD-004 | 2h |
### P1 — 短期 (1 周)
| 修复项 | 对应发现 | 工作量 |
|--------|---------|--------|
| Refresh token 存储到 DB + 事务保护 | AUD-005, 006 | 4h |
| Update handler 添加输入验证 | AUD-017 | 4h |
| Webhook URL 验证防 SSRF | AUD-013 | 1h |
| 磁盘加密确认移至 admin 路由 | AUD-015 | 15min |
| 初始密码写入文件替代 stderr | AUD-016 | 30min |
| 添加 IP 速率限制 | AUD-014 | 2h |
### P2 — 中期 (1 月)
| 修复项 | 对应发现 | 工作量 |
|--------|---------|--------|
| Token 迁移至 HttpOnly Cookie | AUD-010 | 8h |
| CSP 强化 (nonce-based) | AUD-011 | 4h |
| WebSocket ticket 认证 | AUD-012 | 4h |
| 服务器身份验证 (证书固定) | AUD-007 | 8h |
| 服务保护 (恢复策略/看门狗) | AUD-009 | 4h |
| PowerShell 注入面消除 | AUD-008 | 6h |
| HSTS + Permissions-Policy 头 | AUD-024, 036 | 1h |
| 分页补充 | AUD-021, 022 | 4h |
---
## 安全亮点 (做得好的地方)
1. **SQL 注入防御**: 全库一致使用 `sqlx::bind()` 参数化
2. **错误处理**: `ApiResponse::internal_error()` 不泄露内部错误详情
3. **密码哈希**: bcrypt cost=12符合行业标准
4. **Token Family 轮换**: 检测 token 重放并撤销整个 family
5. **常量时间比较**: 注册 token 验证已使用 `constant_time_eq()`
6. **帧速率限制**: 100 帧/5秒/连接
7. **审计日志**: 所有管理员写入操作记录到 `admin_audit_log`
8. **HMAC 心跳**: 设备认证使用 HMAC-SHA256
9. **进程保护列表**: 防止误杀系统关键进程
10. **输入验证**: Create handler 普遍有字段验证