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