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

256 lines
11 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.

# 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 普遍有字段验证